Как я могу провести рефакторинг своего сервиса, используя принцип единой ответственности? - PullRequest
0 голосов
/ 28 марта 2019

Я читаю книгу " Чистый код " ((c) Роберт К. Мартин) и пытаюсь использовать SRP (принцип единственной ответственности). И у меня есть несколько вопросов по этому поводу. В моем приложении есть какой-то сервис, и я не знаю, как я могу реорганизовать его, чтобы он соответствовал правильному подходу. Например, у меня есть сервис:

public interface SendRequestToThirdPartySystemService  {
    void sendRequest();
}

Что делать, если вы посмотрите на название класса? - отправить запрос в стороннюю систему. Но у меня есть эта реализация:

@Slf4j
@Service
public class SendRequestToThirdPartySystemServiceImpl implements SendRequestToThirdPartySystemService {

    @Value("${topic.name}")
    private String topicName;

    private final EventBus eventBus;
    private final ThirdPartyClient thirdPartyClient;
    private final CryptoService cryptoService;
    private final Marshaller marshaller;

    public SendRequestToThirdPartySystemServiceImpl(EventBus eventBus, ThirdPartyClient thirdPartyClient, CryptoService cryptoService, Marshaller marshaller) {
        this.eventBus = eventBus;
        this.thirdPartyClient = thirdPartyClient;
        this.cryptoService = cryptoService;
        this.marshaller = marshaller;
    }

    @Override
    public void sendRequest() {
        try {
            ThirdPartyRequest thirdPartyRequest = createThirdPartyRequest();

            Signature signature = signRequest(thirdPartyRequest);
            thirdPartyRequest.setSignature(signature);

            ThirdPartyResponse response = thirdPartyClient.getResponse(thirdPartyRequest);

            byte[] serialize = SerializationUtils.serialize(response);

            eventBus.sendToQueue(topicName, serialize);

        } catch (Exception e) {
            log.error("Send request was filed with exception: {}", e.getMessage());
        }
    }

private ThirdPartyRequest createThirdPartyRequest() {
    ...
    return thirdPartyRequest;
}

private Signature signRequest(ThirdPartyRequest thirdPartyRequest) {
    byte[] elementForSignBytes = marshaller.marshal(thirdPartyRequest);
    Element element = cryptoService.signElement(elementForSignBytes);
    Signature signature  = new Signature(element);  
    return signature;
}

Что это делает на самом деле? - создать запрос -> подписать этот запрос -> отправить этот запрос -> отправить ответ в очередь

Этот сервис внедряет 4 других сервиса: eventBus, thirdPartyClient, cryptoSevice и marshaller. А в sendRequest метод вызывает каждый этот сервис. Если я хочу создать модульный тест для этого сервиса, мне нужно 4 сервиса. Я думаю, что это слишком много.

Может кто-нибудь указать, как можно изменить эту услугу?

Изменить имя класса и оставить как есть? Разделить на несколько классов? Что-то еще?

1 Ответ

0 голосов
/ 20 мая 2019

СРП хитрый.

Давайте зададим два вопроса:

  • Что такое ответственность?

  • Какие существуют виды ответственности?

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

Ответственность за все в вашем приложении может быть.

Давайте начнем с Модули . Каждый модуль имеет обязанности и может придерживаться SRP.

Тогда этот Модуль может быть изготовлен из Слоев . Каждый Слой несет ответственность и может придерживаться SRP .

Каждый Слой состоит из различных объектов , Функции и т. Д. Каждый Объект и / или Функция имеет обязанности и может придерживаться SRP.

Каждый Объект имеет Методы . Каждый Метод может придерживаться SRP. Объекты могут содержать другие объекты и т. Д.

Каждый Функция или Метод в Объект состоит из операторов и может быть разбит на более Функции / Методы . У каждого утверждения тоже могут быть обязанности.

Давайте приведем пример. Допустим, у нас есть модуль Billing . Если этот модуль реализован в одном огромном классе, придерживается ли этот модуль SRP?

  • С точки зрения системы, модуль действительно придерживается SRP. Тот факт, что это беспорядок, не влияет на этот факт.
  • С точки зрения модуля класс, представляющий этот модуль, не придерживается SRP, поскольку он будет выполнять множество других задач, таких как общение с БД, отправка электронных писем, выполнение бизнес-логики и т. Д.

Давайте посмотрим на различные типы обязанностей.

  • Когда что-то должно быть сделано

  • Как и должно быть куполом

Давайте рассмотрим пример.

public class UserService_v1 {

    public class SomeOperation(Guid userID) {
        var user = getUserByID(userID);
        // do something with the user
    }

    public User GetUserByID(Guid userID) {
        var query = "SELECT * FROM USERS WHERE ID = {userID}";
        var dbResult = db.ExecuteQuery(query);
        return CreateUserFromDBResult(dbResult);
    }

    public User CreateUserFromDBResult(DbResult result) {
        // parse and return User
    }
}

public class UserService_v2 {

    public void SomeOperation(Guid userID) {
        var user = UserRepository.getByID(userID);
        // do something with the user
    }
}

Давайте посмотрим на эти две реализации.

UserService_v1 и UserService_v2 делают одно и то же, но разными способами. С точки зрения системы эти службы придерживаются SRP, поскольку содержат операции, связанные с Users.

Теперь давайте посмотрим, что они на самом деле делают, чтобы завершить свою работу.

UserService_v1 делает эти вещи:

  1. Создает строку SQL-запроса.
  2. Вызывает db для выполнения запроса
  3. Берет определенный DbResult и создает из него User.
  4. Работает ли на User

UserService_v2 делает эти вещи: 1. Запросы из репозитория User по идентификатору 2. Работает ли на User

UserService_v1 содержит:

  • Как сборка для конкретного запроса
  • Как определенный DbResult сопоставляется с пользователем
  • Когда , этот запрос нужно назвать (в начале операции в этом случае)

UserService_v1 содержит:

  • Когда a User следует извлечь из БД

UserRepository содержит:

  • Как определенный запрос является сборкой
  • Как определенный DbResult сопоставлен с User

То, что мы делаем здесь, - это перевод ответственности How с Service на Repository.Таким образом, у каждого класса есть одна причина для изменения .Если как изменяется, мы меняем Repository.Если при изменении , мы меняем Service.

Таким образом, мы создаем объекты, которые взаимодействуют друг с другом для выполнения конкретной работы, путем разделения обязанностей.Хитрость состоит в следующем: какие обязанности мы разделяем ?

Если у нас есть UserService и OrderService, мы не делим , когда и как здесь.Мы делим на , чтобы у нас в системе была одна служба на сущность .

Для этих служб вполне естественно, что им нужны другие объекты для выполнения своей работы.Конечно, мы можем добавить все обязанности что , когда и как к одному объекту, но это только делает беспорядок, нечитабельным и трудно изменить.

В этом отношении SRP помогает нам создавать более чистый код, имея более мелкие части, с которыми сотрудничают и используют друг друга.

Давайтевзгляните на ваш конкретный случай.

Если вы можете переместить ответственность о том, как создан ClientRequest и подписан, переместив его в ThirdPartyClient, ваша SendRequestToThirdPartySystemService будет сообщать только когда этот запрос должен быть отправлен.Это удалит Marshaller и CryptoService как зависимости от вашего SendRequestToThirdPartySystemService.

Также у вас есть SerializationUtils, который вы, вероятно, переименуете в Serializer, чтобы лучше понять намерение, так как Utils является чем-точто мы придерживаемся объектов, которые мы просто не знаем, как называть, и содержат много логики (и, вероятно, множественных обязанностей).

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

Вот версия метода sendRequest с меньшим количеством обязанностей.

@Override
public void sendRequest() {
    try {
        // params are not clear as you don't show them to your code
        ThirdPartyResponse response = thirdPartyClient.sendRequest(param1, param2);

        byte[] serializedMessage = SerializationUtils.serialize(response);

        eventBus.sendToQueue(topicName, serialize);

    } catch (Exception e) {
        log.error("Send request was filed with exception: {}", e.getMessage());
    }
}

Из вашего кода я не уверен, что вы также можете перенести ответственность за сериализацию и десериализацию наEventBus, но если вы можете сделать это, он также удалит Seriazaliation из вашего сервиса.Это сделает EventBus ответственным за , как он сериализовал, и хранит вещи внутри него, делая его более сплоченным.Другие объекты, которые сотрудничают с ним, просто скажут ему отправить и возразить в очередь, не заботясь как эти объекты попадают туда.

...