Pull to refresh

Comments 16

Как все сложно... а проще нельзя структурировать?

можно, но это не будет так хорошо масштабироваться по мере роста проекта

UFO landed and left these words here

для моков интерфейс не нужен. не скажу за другие ЯП, но в Java всё прекрасно мокается без использования интерфейсов

UFO landed and left these words here
пример
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));
  }
}

пример очень простой, но для более сложных случаев разница будет лишь в количестве тестов и моков. поведение мокается в самом тестовом методе, и для разных случаев можно определить разное поведение.

UFO landed and left these words here

Я утверждаю, что вы ломаете контракт (в данном примере контракта вообще нет, но обычно он есть)

что вы подразумеваете под контрактом?

UFO landed and left these words here

Проблема с кодом выше в том, что при изменении имплементации DbClient без изменения его внешнего API тесты могут запросто поломаться

при изменении реализации DbClient сломается интеграционный тест. мок не сломается, т.к. его поведение задаётся в тесте

Спасибо за статью.
Часто бизнес-логика представляет собой последовательность вызовов методов, как например операция "Создание заявки на кредит". Как это может быть реализовано в предложенном вами подходе? Что думаете про использование паттерна фасад для выделения законченного бизнес сценария?

  1. если такая операция вызывается только извне, создаётся обработчик для неё (handler/operation), последовательно вызывающий шаги (service)

  2. можно использовать движок бизнес-процессов, где "создание заявки на кредит" выделено в отдельный процесс, который можно переиспользовать. и вызываться он будет непосредственно движком БП.

  3. если операция должна переиспользоваться в рамках приложения, можно сделать класс типа "сервис", который и будет фасадом. но в случае, если это какие-то крупные шаги, придётся всё-таки нарушить правило "сервис не вызывает сервис".

  4. если данная операция может выполнятся асинхронно, можно реализовать отправку сообщения в рамках одного приложения, использовав либо внешний брокер, либо возможности языка/фреймворка, чтобы положить событие в какую-то внутреннюю очередь. дальнейшая обработка согласно п. 1.

Основная проблема в том, что классы из пакета service начинают вызывать друг друга, а цепочки таких вызовов становятся очень длинными.

Типовой подход для пакета service состоит в разбиении логики на 2 части - application logic и domain logic. Элемент функционала application logic может обращаться к domain logic и не может делать вызовы к другим элементам application logic. Domain logic состоит из доменных сервисов и агрегатов/сущностей домена. Доменные сервисы обычно используются когда надо работать одновременно с более чем одним агрегатом. Всё это детально описано в огромном количестве литературы по domain driven design.

UFO landed and left these words here

Не любой "внутренний" сервис это доменная логика. Может быть например сервис загрузки файлов в файловое хранилище, который добавляет запись о загруженном файле в базу и возвращает id записи, который можно указать в другой сущности. Но нет сценариев, когда он может вызываться напрямую из контроллера.

Функционал application logic взаимодействует не только с доменной логикой, но и другим блоком функционала, который работает с внешними источниками данных.

Sign up to leave a comment.

Articles