
Комментарии 16
Как все сложно... а проще нельзя структурировать?
для моков интерфейс не нужен. не скажу за другие ЯП, но в Java всё прекрасно мокается без использования интерфейсов
пример
public class MyService {
private final DbClient dbClient;
public void doSomething(Request request) {
Long id = dbClient.getLastId();
dbClient.update(id, request);
}
}
public class DbClient {
public Long getLastId() {
//запрос в БД
}
public void update(Long id, Request request) {
//запрос в БД
}
}
public class MyServiceTest {
private MyService myService;
public void doSomethingTest() {
DbClient dbClient = mock(DbClient.class);
when(dbClient.getLastId()).thenReturn(1);
doNothing().when(dbClient.update(any(), any()));
Request request = createRequest();
myService.doSomething();
verify(dbClient).getLastId();
veryfy(dbClient).update(eq(1), eq(request));
}
}пример очень простой, но для более сложных случаев разница будет лишь в количестве тестов и моков. поведение мокается в самом тестовом методе, и для разных случаев можно определить разное поведение.
Я утверждаю, что вы ломаете контракт (в данном примере контракта вообще нет, но обычно он есть)
что вы подразумеваете под контрактом?
Спасибо за статью.
Часто бизнес-логика представляет собой последовательность вызовов методов, как например операция "Создание заявки на кредит". Как это может быть реализовано в предложенном вами подходе? Что думаете про использование паттерна фасад для выделения законченного бизнес сценария?
если такая операция вызывается только извне, создаётся обработчик для неё (handler/operation), последовательно вызывающий шаги (service)
можно использовать движок бизнес-процессов, где "создание заявки на кредит" выделено в отдельный процесс, который можно переиспользовать. и вызываться он будет непосредственно движком БП.
если операция должна переиспользоваться в рамках приложения, можно сделать класс типа "сервис", который и будет фасадом. но в случае, если это какие-то крупные шаги, придётся всё-таки нарушить правило "сервис не вызывает сервис".
если данная операция может выполнятся асинхронно, можно реализовать отправку сообщения в рамках одного приложения, использовав либо внешний брокер, либо возможности языка/фреймворка, чтобы положить событие в какую-то внутреннюю очередь. дальнейшая обработка согласно п. 1.
Основная проблема в том, что классы из пакета service начинают вызывать друг друга, а цепочки таких вызовов становятся очень длинными.
Типовой подход для пакета service состоит в разбиении логики на 2 части - application logic и domain logic. Элемент функционала application logic может обращаться к domain logic и не может делать вызовы к другим элементам application logic. Domain logic состоит из доменных сервисов и агрегатов/сущностей домена. Доменные сервисы обычно используются когда надо работать одновременно с более чем одним агрегатом. Всё это детально описано в огромном количестве литературы по domain driven design.
Не любой "внутренний" сервис это доменная логика. Может быть например сервис загрузки файлов в файловое хранилище, который добавляет запись о загруженном файле в базу и возвращает id записи, который можно указать в другой сущности. Но нет сценариев, когда он может вызываться напрямую из контроллера.
Идеальная структура сервиса