Чистая архитектура дяди Боба - класс сущности / модели для каждого слоя?

44

ЗАДНИЙ ПЛАН :

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

Что я заметил

В каждом слое (презентация, домен и данные) есть класс модели для одной и той же сущности (говорящий на UML). Кроме того, существуют классы отображения, которые заботятся о преобразовании объекта всякий раз, когда данные пересекают границы (от слоя к другому).

ВОПРОС:

Обязательно ли иметь классы моделей в каждом слое, когда я знаю, что все они будут иметь одинаковые атрибуты, если понадобятся все операции CRUD? Или это правило или лучшая практика при использовании чистой архитектуры?

Рами Джемли
источник

Ответы:

52

На мой взгляд, это совсем не так, как это подразумевается. И это нарушение СУХОЙ.

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

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

Начнем с того, что чистая архитектура была создана с учетом другой типичной среды / сценария. Приложения для бизнес-серверов с огромными внешними слоями, которым нужны специальные типы объектов. Например, базы данных, которые производят SQLRowобъекты и нуждаются SQLTransactionsв возврате к элементам обновления. Если бы вы использовали их в центре, вы бы нарушили направление зависимости, потому что ваше ядро ​​зависело бы от базы данных.

С облегченными ORM, которые загружают и хранят объекты сущностей, это не так. Они делают сопоставление между своим внутренним SQLRowи вашим доменом. Даже если вам нужно поместить @Entitiyаннотацию ORM в объект вашего домена, я бы сказал, что это не устанавливает «упоминание» о внешнем уровне. Поскольку аннотации - это просто метаданные, никакой код, который их не ищет, их не увидит. И что более важно, ничего не нужно менять, если вы удалите их или замените их аннотацией другой базы данных.

Напротив, если вы меняете свой домен, и вы сделали все эти мапперы, вам придется многое изменить.


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

А именно следующее здесь https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

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

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

Кроме того, он также разделяет слои, поэтому изменения в ядре не обязательно требуют изменений во внешних слоях (см. Комментарий SteveCallender). В этом контексте легко увидеть, как объекты должны конкретно представлять цель, для которой они используются. Кроме того, эти слои должны общаться друг с другом в терминах объектов, которые созданы специально для целей этой коммуникации. Это может даже означать, что существует 3 представления, по 1 на каждом уровне, 1 для передачи между уровнями.

И есть https://blog.8thlight.com/uncle-bob/2011/11/22/Clean-Architecture.html, адрес которого указан выше:

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

Это IMO подразумевает, что простое копирование объектов 1: 1 является запахом в архитектуре, потому что вы на самом деле не используете надлежащие слои и / или абстракции.

Позже он объясняет, как он представляет все «копирование»

Вы отделяете пользовательский интерфейс от бизнес-правил, передавая простые структуры данных между ними. Вы не позволяете своим контролерам ничего знать о бизнес-правилах. Вместо этого контроллеры распаковывают объект HttpRequest в простую ванильную структуру данных, а затем передают эту структуру данных объекту-интерактору, который реализует сценарий использования, вызывая бизнес-объекты. Затем интерактор собирает данные ответа в другую ванильную структуру данных и передает их обратно в пользовательский интерфейс. Представления не знают о бизнес-объектах. Они просто смотрят в эту структуру данных и представляют ответ.

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

Однако применительно к простому приложению Android, такому как средство просмотра фотографий, в котором у Photoсущности имеется около 0 бизнес-правил, а «сценарий использования», который их касается, практически отсутствует и на самом деле больше заботится о кэшировании и загрузке (этот процесс должен выполняться IMO. более подробно), точка для создания отдельных представлений о фотографии начинает исчезать. У меня даже возникает ощущение, что сама фотография является объектом передачи данных, в то время как реальный уровень бизнес-логики отсутствует.

Существует различие между «отделить пользовательский интерфейс от бизнес-правил путем передачи простых структур данных между ними» и «когда вы хотите отобразить фотографию, переименуйте ее 3 раза в пути» .

Кроме того, я вижу, что эти демонстрационные приложения не могут представить чистую архитектуру, потому что они придают огромное значение разделению слоев ради разделения слоев, но эффективно скрывают, что делает приложение. Это противоречит тому, что сказано в https://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html, а именно:

архитектура программного приложения кричит о вариантах использования приложения

Я не вижу такого акцента на разделении слоев в чистой архитектуре. Речь идет о направлении зависимостей и сосредоточении внимания на том, чтобы представлять ядро ​​приложения - сущности и варианты использования - в идеально простой java без зависимостей вовне. Дело не столько в зависимости от этого ядра.

Поэтому, если ваше приложение на самом деле имеет ядро, которое представляет бизнес-правила и варианты использования, и / или разные люди работают на разных уровнях, разделите их по назначению. Если вы просто пишете простое приложение, не переусердствуйте. 2 слоя с плавными границами может быть более чем достаточно. И слои могут быть добавлены позже.

zapl
источник
1
@RamiJemli В идеале сущности одинаковы во всех приложениях. В этом разница между «корпоративными бизнес-правилами» и «бизнес-правилами приложения» (иногда бизнес-логика приложения). Ядро - это очень абстрактное представление ваших сущностей, достаточно общее, чтобы вы могли использовать его везде. Представьте себе банк, в котором есть много приложений, одно для поддержки клиентов, другое для банкоматов, другое для веб-интерфейса для самих клиентов. Все они могут использовать то же самое, BankAccountно с конкретными правилами приложения, что вы можете делать с этой учетной записью.
4
Я думаю, что важным моментом в чистой архитектуре является то, что, используя слой интерфейсного адаптера для преобразования (или, как вы говорите, карты) между представлением сущности разных слоев, вы уменьшаете зависимость от упомянутой сущности. Если в слоях Usecase или Entity произойдут изменения (надеюсь, маловероятно, но по мере изменения требований эти уровни будут изменяться), то влияние изменений будет заключено в уровне адаптера. Если вы решите использовать одно и то же представление сущности во всей вашей архитектуре, влияние этого изменения будет намного больше.
SteveCallender
1
@RamiJemli хорошо использовать фреймворки, которые делают жизнь проще, дело в том, что вы должны быть осторожны, когда ваша архитектура опирается на них, и вы начинаете ставить их в центр всего. Вот даже статья о RxJava blog.8thlight.com/uncle-bob/2015/08/06/let-the-magic-die.html - это не говорит, что вы не должны использовать это. Это больше похоже на: я видел это, через год все будет по-другому, и когда ваше приложение все еще рядом, вы застряли с ним. Сделайте это подробно и сделайте самые важные вещи в простой старой Java, применяя простые старые принципы SOLID.
Запл
1
@zapl Как вы относитесь к слою веб-сервисов? Другими словами, вы бы поместили @SerializedNameаннотации Gson в модель предметной области? Или вы бы создали новый объект, отвечающий за отображение веб-ответа на модель домена?
tir38
2
@ tir38 Само по себе разделение не дает выгоды, оно связано с затратами на будущие изменения. => Зависит от приложения. 1) вам потребуется время для создания и поддержки добавленной стадии, которая трансформируется между различными представлениями. Например, добавление поля в домен и забывание добавить его куда-то еще не случайно. Не может случиться с простым подходом. 2) Стоит перейти к более сложной настройке позже, если окажется, что она вам нужна. Добавлять слои непросто, поэтому в больших приложениях проще обосновать больше слоев, которые не нужны немедленно
zapl
7

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

Например: у вас есть бизнес-метод createX (имя строки), а затем у вас может быть метод createX (имя строки) на уровне DAO, вызываемый внутри бизнес-метода. Они могут иметь одну и ту же подпись и, возможно, есть только делегация, но у них разные цели. Вы также можете использовать createX (имя строки) в UseCase. Даже тогда это не является избыточным. Я имею в виду следующее: «Одни и те же подписи не означают семантику». Выберите другие имена, чтобы очистить семантику. Называя себя, это никак не влияет на SRP.

UseCase отвечает за логику для конкретного приложения, бизнес-объект отвечает за логику, не зависящую от приложения, а DAO отвечает за хранение.

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

Вы можете думать о разных аспектах одной и той же семантики. Пользовательский объект должен отображаться на экране, он имеет некоторые внутренние правила согласованности и должен где-то храниться. Каждый аспект должен быть представлен в другом классе (SRP). Создание картографов может быть проблемой в заднице, поэтому в большинстве проектов, над которыми я работал, эти аспекты сведены в один класс. Это явно нарушение ПСП, но на самом деле это никого не волнует.

Я называю применение чистой архитектуры и SOLID «социально неприемлемым». Я бы работал с этим, если бы мне позволили. В настоящее время мне запрещено это делать. Я жду момента, когда мы должны подумать о том, чтобы воспринимать SOLID серьезно.

user205407
источник
Я думаю, что ни один метод на уровне данных не должен иметь такую ​​же сигнатуру, как любой метод на уровне домена. На уровне домена вы используете связанные с бизнесом соглашения об именах, такие как signUp или login, а на уровне данных вы используете save (если шаблон DAO) или add (если Repository, потому что этот шаблон использует Collection в качестве метафоры). Наконец, я не говорю о сущностях (Data) и модели (Domain), я подчеркиваю бесполезность UserModel и его Mapper (уровня представления). Вы можете вызвать класс User домена внутри презентации, и это не нарушает правило зависимости.
Рами Джемли
Я согласен с Рами, что отображение не нужно, потому что вы можете сделать отображение непосредственно в реализации интерактора.
Кристофер Перри
5

Нет, вам не нужно создавать классы моделей в каждом слое.

Entity ( DATA_LAYER) - это полное или частичное представление объекта базы данных.DATA_LAYER

Mapper ( DOMAIN_LAYER) - фактически это класс, который конвертирует Entity в ModelClass, который будет использоваться наDOMAIN_LAYER

Посмотрите: https://github.com/lifedemons/photoviewer

deathember
источник
1
Конечно, я не против сущностей на уровне данных, но, в вашем примере, класс PhotoModel на уровне представления имеет те же атрибуты класса Photo на уровне домена. Это технически тот же класс. это необходимо?
Я думаю, что в вашем примере что-то не так, поскольку уровень домена не должен зависеть от других уровней, так как в вашем примере ваши средства отображения зависят от сущностей в вашем уровне данных, которые, по IMO, должны быть наоборот
navid_gh