Обновить

Идеальная структура сервиса

Уровень сложностиСредний
Время на прочтение12 мин
Охват и читатели5K
Всего голосов 7: ↑6 и ↓1+5
Комментарии16

Комментарии 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));
  }
}

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

НЛО прилетело и опубликовало эту надпись здесь

Проблема с кодом выше в том, что при изменении имплементации 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.

НЛО прилетело и опубликовало эту надпись здесь

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

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации