База данных находится на внешнем уровне, но как это будет работать в реальности?
Вы создаете независимый от технологии интерфейс на уровне шлюза и внедряете его на уровне базы данных. Э.Г.
public interface OrderRepository {
public List<Order> findByCustomer(Customer customer);
}
реализация находится на уровне базы данных
public class HibernateOrderRepository implements OrderRepository {
...
}
Во время выполнения внутренние слои внедряются с реализациями внешних слоев. Но у вас нет зависимости исходного кода.
Это можно увидеть, просканировав свои операторы импорта.
И один из вариантов использования будет управлять людьми. Manage Persons сохраняет / извлекает / .. Persons (=> операции CRUD), но для этого сценарий использования должен общаться с базой данных. Но это было бы нарушением правила зависимости
Нет, это не будет нарушать правило зависимости, потому что варианты использования определяют необходимый интерфейс. БД просто реализует это.
Если вы управляете зависимостями вашего приложения с помощью maven, вы увидите, что модуль db jar зависит от вариантов использования, а не наоборот. Но было бы еще лучше извлечь интерфейс этих вариантов использования в собственный модуль.
Тогда зависимости модуля будут выглядеть так
+-----+ +---------------+ +-----------+
| db | --> | use-cases-api | <-- | use cases |
+-----+ +---------------+ +-----------+
это инверсия зависимостей, которая иначе выглядела бы так
+-----+ +-----------+
| db | <-- | use cases |
+-----+ +-----------+
Если я получу запрос GET / person / {id}, должен ли мой Микросервис обрабатывать его следующим образом?
Да, это было бы нарушением, поскольку веб-слой обращается к слою БД. Лучшим подходом является то, что веб-уровень обращается к уровню контроллера, который обращается к уровню варианта использования и т. Д.
Чтобы сохранить инверсию зависимостей, вы должны отделить слои, используя интерфейсы, как я показал выше.
Поэтому, если вы хотите передать данные на внутренний уровень, вы должны ввести интерфейс на внутреннем уровне, который определяет методы для получения необходимых данных и их реализации на внешнем уровне.
В слое контроллера вы будете указывать такой интерфейс
public interface ControllerParams {
public Long getPersonId();
}
на веб-уровне вы можете реализовать свой сервис следующим образом
@Path("/person")
public PersonRestService {
// Maybe injected using @Autowired if you are using spring
private SomeController someController;
@Get
@Path("{id}")
public void getPerson(PathParam("id") String id){
try {
Long personId = Long.valueOf(id);
someController.someMethod(new ControllerParams(){
public Long getPersonId(){
return personId;
}
});
} catch (NumberFormatException e) {
// handle it
}
}
}
На первый взгляд это похоже на стандартный код. Но имейте в виду, что вы можете позволить остальной платформе десериализовать запрос в объект Java. И этот объект может реализовать ControllerParams
вместо.
Если вы будете следовать правилу инверсии зависимостей и чистой архитектуре, вы никогда не увидите оператор импорта класса внешнего уровня во внутреннем уровне.
Целью чистой архитектуры является то, что основные бизнес-классы не зависят от какой-либо технологии или среды. Поскольку зависимости указывают от внешнего к внутреннему слоям, единственной причиной изменения внешнего слоя является изменение внутреннего слоя. Или если вы обменяетесь на технологию реализации внешнего уровня. Например. Отдых -> МЫЛО
Так зачем нам делать это усилие?
Роберт К. Мартин рассказывает об этом в главе 5 «Объектно-ориентированное программирование». В конце раздела инверсии зависимостей он говорит:
При таком подходе архитекторы программного обеспечения, работающие в системах, написанных на языках ОО, имеют абсолютный контроль над направлением всех зависимостей исходного кода в системе. Они не обязаны выравнивать эти зависимости с потоком управления. Независимо от того, какой модуль вызывает и какой модуль вызывается, разработчик программного обеспечения может указать зависимость исходного кода в любом направлении.
Это сила!
Я полагаю, что разработчики часто не понимают поток управления и зависимость от исходного кода. Поток управления обычно остается тем же самым, но зависимости исходного кода инвертированы. Это дает нам возможность создавать подключаемые архитектуры. Каждый интерфейс является точкой подключения. Таким образом, его можно заменить, например, по техническим причинам или для испытаний.
EDIT
уровень шлюза = interface OrderRepository => не должен ли OrderRepository-Interface находиться внутри UseCases, потому что мне нужно использовать операции crud на этом уровне?
Я думаю, что можно переместить OrderRepository
в слой варианта использования. Другой вариант - использовать входные и выходные порты варианта использования. Порт ввода варианта использования может иметь методы, подобные хранилищу, например, findOrderById
, и адаптирует его к OrderRepository
. Для устойчивости он может использовать методы, которые вы определили в выходном порту.
public interface UseCaseInputPort {
public Order findOrderById(Long id);
}
public interface UseCaseOutputPort {
public void save(Order order);
}
Отличие от использования OrderRepository
состоит в том, что порты вариантов использования содержат только методы репозитория для конкретных вариантов использования. Таким образом, они изменяются только в случае изменения варианта использования. Таким образом, они несут единоличную ответственность, и вы соблюдали принцип сегрегации интерфейса.