Корпоративные серверные приложения — Примеры
Пример 1 — Данные одного пользователя в запросе другого
Панель поддержки время от времени показывает тикеты одного клиента другому аккаунту. Класс ниже хранит клиента того, кого обслуживают прямо сейчас, а один его экземпляр создаётся при запуске приложения. Покажите область видимости, которая вызывает утечку, и объект, который её убирает.
// создаётся один раз на приложение и разделяется всеми запросами
class RequestContext {
private Customer current;
void setCustomer(Customer customer) { this.current = customer; }
Customer customer() { return current; }
}
Решение
// создаётся на каждый запрос и внедряется в то, что его обслуживает
class RequestContext {
private final Customer current;
RequestContext init(Customer customer) { ... }
Customer customer() { return current; }
}
Фреймворк обычно предлагает один экземпляр на запрос, один на приложение и связывает объект уровня запроса с обработчиком этого запроса; именно хранение клиента в копии уровня приложения позволяет параллельному запросу его перезаписать. Однако ошибка — тянуться к общему держателю, потому что передавать значение через параметры утомительно. Утечку тогда обнаруживают только под конкурентностью. Решите область видимости заранее и храните состояние запроса только в ней.
Пример 2 — Граница транзакции, выставленная по удобству
Перевод списывает деньги с одного счёта и зачисляет на другой, но каждый шаг фиксируется отдельно, потому что граница взята вокруг каждого метода, и отказ между ними оставляет деньги пропавшими. Перенесите границу так, чтобы пара была атомарной.
// граница вокруг каждой записи: одна транзакция на вызов
void debit(Account account, Money amount) { ... }
void credit(Account account, Money amount) { ... }
void transfer(Account from, Account to, Money amount) {
debit(from, amount);
credit(to, amount);
}
Решение
// граница вокруг всей операции
void transfer(Account from, Account to, Money amount) {
debit(from, amount);
credit(to, amount);
}
void debit(Account account, Money amount) { ... } // без собственной границы
void credit(Account account, Money amount) { ... }
Граница решает, что атомарно, поэтому взятая вокруг пары она фиксирует обе записи или ни одну. Взятая на уровень выше, она вместо этого держит блокировки всё время несвязанной работы. Потому её расположение — решение о корректности, а не деталь конфигурации. Ошибка — оставить границу там, куда случайно попала аннотация, и узнать об этом только при частичной записи.