Шаблон проектирования чистой архитектуры - PullRequest
0 голосов
/ 16 сентября 2018

enter image description here

https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

У меня есть вопрос по этому шаблону. База данных находится на внешнем уровне, но как это будет работать в реальности? Например, если у меня есть Microservices, который просто управляет этой сущностью:

person{
  id,
  name,
  age
}

И один из вариантов использования будет управлять людьми. Manage Persons сохраняет / извлекает / .. Persons (=> операции CRUD), но для этого сценарий использования должен общаться с базой данных. Но это было бы нарушением правила зависимости

Главное правило, которое заставляет эту архитектуру работать, - это правило зависимости. Это правило гласит, что зависимости исходного кода могут указывать только внутрь.

  1. Будет ли это даже допустимым вариантом использования?
  2. как я могу получить доступ к базе данных, если она находится на внешнем слое? (Зависимость Iversion?)

Если я получу запрос GET /person/{id}, должен ли мой Микросервис обрабатывать его следующим образом?

enter image description here

Но использование инверсии зависимости будет нарушением

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


Пересечение границ. В правом нижнем углу диаграммы приведен пример о том, как мы пересекаем границы круга. Он показывает контроллеры и Докладчики общаются с вариантами использования на следующем уровне. Заметка поток контроля. Он начинается в контроллере, проходит через использовать случай, а затем завершает выполнение в презентере. Обратите внимание также на зависимости исходного кода. Каждый из них указывает внутрь к варианты использования.

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

Например, учтите, что в случае использования необходимо вызвать докладчика. Однако этот призыв не должен быть прямым, потому что это нарушит Правило зависимости: имя во внешнем круге не может быть упомянуто внутренний круг. Таким образом, у нас есть сценарий использования вызова интерфейса (показано здесь как Используйте Case Output Port) во внутреннем круге, и ведущий должен находиться в внешний круг его реализует.

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

Если уровень варианта использования объявляет интерфейс репозитория, который будет реализован пакетом БД (уровень Frameworks & Drivers)

enter image description here

Если сервер получит GET /persons/1 запрос, PersonRest создаст PersonRepository и передаст этот репозиторий + идентификатор функции ManagePerson :: getPerson, getPerson не знает PersonRepository, но знает интерфейс, который он реализует, поэтому он не нарушает никаких правил право? ManagePerson :: getPerson будет использовать этот репозиторий для поиска сущности и вернет Person Person в PersonRest :: get, который вернет Json Objekt клиенту, верно?

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

Ты заранее

Ответы [ 2 ]

0 голосов
/ 16 сентября 2018

База данных находится на внешнем уровне, но как это будет работать в реальности?

Вы создаете независимый от технологии интерфейс на уровне шлюза и внедряете его на уровне базы данных. Э.Г.

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}, должен ли мой Микросервис обрабатывать его следующим образом? enter image description here

Да, это было бы нарушением, поскольку веб-слой обращается к слою БД. Лучшим подходом является то, что веб-уровень обращается к уровню контроллера, который обращается к уровню варианта использования и т. Д.

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

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

В слое контроллера вы будете указывать такой интерфейс

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 состоит в том, что порты вариантов использования содержат только методы репозитория для конкретных вариантов использования. Таким образом, они изменяются только в случае изменения варианта использования. Таким образом, они несут единоличную ответственность, и вы соблюдали принцип сегрегации интерфейса.

0 голосов
/ 16 сентября 2018

Ключевым элементом является инверсия зависимостей.Ни один из внутренних слоев не должен иметь зависимостей от внешних слоев.Таким образом, если, например, уровню Use Case необходимо вызвать хранилище базы данных, вы должны определить интерфейс хранилища (просто интерфейс, без какой-либо реализации) внутри уровня Use Case и поместить его реализацию на уровень Interface Adapters.

...