Я прочитал книгу "Чистый код" ((c) Роберт С. Мартин) и пытаюсь использовать СРП (принцип единой ответственности). И у меня есть несколько вопросов по этому поводу. У меня есть некоторая служба в моем приложении, и я не знаю, как ее реорганизовать, чтобы она соответствовала правильному подходу. Например, у меня есть сервис:
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. А в методе послать запрос вызывается каждый этот сервис.
Если я хочу создать модульный тест для этой службы, мне нужно смоделировать 4 службы. Я думаю, что это слишком.
Может ли кто-нибудь указать, как можно изменить эту услугу?
Изменить название класса и оставить как есть? Разделить на несколько классов? Что-то другое?




SRP — сложная штука.
Зададим два вопроса:
Что такое ответственность?
Какие бывают виды ответственности?
Одна важная вещь об обязанностях заключается в том, что они имеют Объем, и вы можете определить их на разных уровнях Гранулярность. и носят иерархический характер.
Все в вашем приложении может иметь ответственность.
Начнем с Модули. Каждый модуль имеет обязанности и может придерживаться SRP.
Тогда этот Модуль можно сделать из Слои. Каждый Слой несет ответственность и может придерживаться СРП.
Каждый Слой состоит из разных Объекты, Функции и т. д. Каждый Объект и/или Функция имеет обязанности и может придерживаться 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 делает следующее:
db для выполнения запросаDbResult и создает из него User.UserUserService_v2 делает следующее:
1. Запрашивает из репозитория User по ID
2. Делает операцию на User
UserService_v1 содержит:
DbResult сопоставлен с пользователемUserService_v1 содержит:
User должен быть получен из БДUserRepository содержит:
DbResult сопоставляется с UserЗдесь мы переносим ответственность Как с 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 ответственным за как, он будет сериализован и сохранит вещи внутри него, что сделает его более связным. Другие объекты, которые сотрудничают с ним, просто скажут ему отправить и возразить в очередь, не заботясь о том, что как эти объекты туда попадают.
Вы можете задать этот вопрос в softwareengineering.stackexchange.com