Реализация паттерна Repository в браузерном JavaScript

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

Содержание

Таким образом, у Вас появляется полная абстракция от источника данных, будь то REST-API, MBaaS, SaaS, IndexedDB, HTML, сторонний сервис по протоколу JSON-RPC или Service Stub.

“Однако мы часто забываем, что принятие решений лучше всего откладывать до последнего момента. Дело не в лени или безответственности; просто это позволяет принять информированное решение с максимумом возможной информации. Преждевременное решение принимается на базе неполной информации. Принимая решение слишком рано, мы лишаемся всего полезного, что происходит на более поздних стадиях: обратной связи от клиентов, возможности поразмышлять над текущим состоянием проекта и опыта применения решений из области реализации.”

“We often forget that it is also best to postpone decisions until the last possible moment. This isn’t lazy or irresponsible; it lets us make informed choices with the best possible information. A premature decision is a decision made with suboptimal knowledge. We will have that much less customer feedback, mental reflection on the project, and experience with our implementation choices if we decide too soon.”

- “Clean Code: A Handbook of Agile Software Craftsmanship” [1]

“A good architecture allows major decision to be deferred!” - Robert Martin

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

“You would make big decisions as late in the process as possible, to defer the cost of making the decisions and to have the greatest possible chance that they would be right. You would only implement what you had to, in hopes that the needs you anticipate for tomorrow wouldn’t come true.”

- Kent Beck [12]

“The best architects remove architecture by figuring out how to make things shiftable.” - Martin Fowler

Кроме того, у Вас появляется возможность реализовать паттерны Identity Map и Unit of Work. Последний часто востребован, так как позволяет выполнить откат состояния локальных объектов в случае, если сохранить данные невозможно (пользователь передумал или ввел невалидные данные).

Модель предметной области (Domain Model)

Наибольшим преимуществом полноценных Моделей предметной области в программе является возможность использования принципов Domain-Driven Design (DDD) [5]. Если Модели содержат исключительно бизнес-логику, и освобождены от служебной логики, то они могут легко читаться специалистами предметной области (т.е. представителем заказчика). Это освобождает Вас от необходимости создания UML-диаграмм для обсуждений и позволяет добиться максимально высокого уровня взаимопонимания, продуктивности, и качества реализации моделей.

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

“Весь смысл объектов в том, что они позволяют хранить данные вместе с процедурами их обработки. Классический пример дурного запаха – метод, который больше интересуется не тем классом, в котором он находится, а каким то другим. Чаще всего предметом зависти являются данные.”

“The whole point of objects is that they are a technique to package data with the processes used on that data. A classic smell is a method that seems more interested in a class other than the one it actually is in. The most common focus of the envy is the data.”

- “Refactoring: Improving the Design of Existing Code” [8]

“Хороший дизайн размещает логику рядом с данными, в отношении которых она действует.”

“Good design puts the logic near the data it operates on.”

- Kent Beck [12]

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

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

“If the framework’s partitioning conventions pull apart the elements implementing the conceptual objects, the code no longer reveals the model.

There is only so much partitioning a mind can stitch back together, and if the framework uses it all up, the domain developers lose their ability to chunk the model into meaningful pieces.”

- “Domain-Driven Design: Tackling Complexity in the Heart of Software” [5]

Это привело к такому огромному количеству хитросплетений слушателей, что превосходство в performance было утрачено, но еще раньше была утрачена читаемость кода. Даже я не мог на следующий день сказать что делает тот или иной фрагмент кода, не говоря уже о специалисте предметной области. Мало того, что это в корне разрушало принципы Domain-Driven Design, так это еще и в значительной мере снижало скорость разработки новых функций проекта.

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

Впоследствии модели вернули себе свои концептуальные контуры и читаемость кода, push-алгоритм был заменен на pull-алгоритм (точнее, на hybrid push-pull), и, вместе с тем, был сохранен механизм реакций при добавлении, изменении или удалении объектов. Для достижения этого результата пришлось своими силами создать библиотеку реализующую паттерн Repository, так как существующих решений для реляционных данных с качественной кодовой базой я не смог найти. Получилось что-то вроде Object-Relational Mapping (ORM) для JavaScript, включая паттерн Data Mapper (данные могут трансформироваться (отображаться) между объектами и постоянным хранилищем данных).

Парадигма реактивного программирования

Сегодня модно увлекаться реактивным программированием. Знаете ли Вы, что разработчики dojo впервые применили реактивное программирование в своей реализации паттерна Repository еще 13 сентября 2010?

Реактивное программирование дополняет (а не противопоставляет) паттерн Repository, о чем красноречиво свидетельствует опыт dojo.store и Dstore.

Разработчики dojo - команда высококвалифицированных специалистов, чьи библиотеки используют такие серьезные компании как IBM. Примером того, насколько серьезно и комплексно они подходят к решению проблем, может служить история библиотеки RequireJS.

Примеры реализаций паттерна Repository и ORM в JavaScript

Примеры простейших реализаций паттерна Repository на JavaScript в проекте todomvc.com:

Другие реализации:

  • Dstore - yet another excellent implementation of Repository pattern.
  • Dojo1 Store - Dojo1 implementation of Repository pattern.
  • JS-Data - Object-Relational Mapping (ORM) written by JavaScript for relational data. Does not support composite relations.
  • 9 JavaScript Libraries for Working with Local Storage - article with interesting comments.
  • Kinvey Data Store - implementation of Repository pattern by MBaaS Kinvey, source code
  • Pocket.js - a wrapper for the window.localStorage. It provides helpful methods which utilise MongoDB’s proven syntax and provides a powerful lightweight abstraction from the complexity of managing and querying local storage.
  • ZangoDB is a MongoDB-like interface for HTML5 IndexedDB that supports most of the familiar filtering, projection, sorting, updating and aggregation features of MongoDB, for usage in the web browser (source code).
  • JsStore is SQL Like IndexedDb Wrapper. It provides simple api to store, retrieve, delete, remove, and for other advanced Database functionalities (source code).
  • ODATA libraries - multiple implementations of Open Data Protocol (ODATA).
  • Minimongo - A client-side MongoDB implementation which supports basic queries, including some geospatial ones.

Я не могу добавить сюда Ember.js, так как он реализует паттерн ActiveRecord.

Отдельно стоит упомянуть библиотеку rql, которая позволяет легко реализовывать паттерны Service Stub и Repository. Много наработок можно увидеть в проектах persvr и kriszyp.

В текущей статье не рассматриваются примеры реализаций паттернов Event Sourcing и CQRS (о чем ведется речь в статье “Роль сервисного слоя в CQRS и Event Sourcing на примере использования Redux в Angular”):

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

Отдельно стоит упомянуть реализацию реактивных хранилищ основанных на состоянии с использованием библиотеки RxJS, смотрите, например, angular2-rxjs-chat.

Виды связей по осведомленности

Однонаправленные ассоциации

Однонаправленные связи (unidirectional associations).

Двунаправленные ассоциации

Двунаправленные ассоциации (Bidirectional Associations)

Двунаправленная ассоциация – это пара свойств, связанных в противоположных направлениях. Класс Car (Автомобиль) имеет свойство owner:Person[1], а класс Person (Личность) имеет свойство cars:Car[*].

A bidirectional association is a pair of properties that are linked together as inverses. The Car class has property owner:Person[1], and the Person class has a property cars:Car[*].

- “UML Distilled. A Brief Guide to the Standard Object Modeling Language” 3d edition by Martin Fowler

Проектирование ассоциаций

В DDD важную роль играет направление связей, и соблюдение принципа минимальной достаточности (“дистилляция моделей” [5]).

In real life, there are lots of many-to-many associations, and a great number are naturally bidirectional. The same tends to be true of early forms of a model as we brainstorm and explore the domain. But these general associations complicate implementation and maintenance. Furthermore, they communicate very little about the nature of the relationship.

There are at least three ways of making associations more tractable.

  1. Imposing a traversal direction
  2. Adding a qualifier, effectively reducing multiplicity
  3. Eliminating nonessential associations

It is important to constrain relationships as much as possible. A bidirectional association means that both objects can be understood only together. When application requirements do not call for traversal in both directions, adding a traversal direction reduces interdependence and simplifies the design. Understanding the domain may reveal a natural directional bias.

- “Domain-Driven Design: Tackling Complexity in the Heart of Software” [5]

Minimalist design of associations helps simplify traversal and limit the explosion of relationships somewhat, but most business domains are so interconnected that we still end up tracing long, deep paths through object references. In a way, this tangle reflects the realities of the world, which seldom obliges us with sharp boundaries. It is a problem in a software design.

- “Domain-Driven Design: Tackling Complexity in the Heart of Software” [5]

Виды связей по владению

Ассоциация

Ассоциация (association) — это отношение между классами (или точнее, экземплярами этих классов), отражающая некоторое значимые и полезные связи между ними. В языке UML ассоциации описываются как “семантические взаимосвязи между двумя или несколькими классификаторами и их экземплярами”.

Ассоциация (association) - Описание набора однородных связей между объектами двух классов.

Квалифицированная ассоциация (Qualified association) - Ассоциация, участники которой определяются значением квалификатора.

An association is a relationship between classes (more precisely, instances of those classes) that indicates some meaningful and interesting connection. In the UML, associations are defined as “the semantic relationship between two or more classifiers that involve connections among their instances.”

Association - A description of a related set of links between objects of two classes.

Qualified association - An association whose membership is partitioned by the value of a qualifier.

- “Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development” [13] by Craig Larman

Агрегация

Сперва рассмотрим агрегацию с позиции UML.

Агрегация в UML

Агрегация (aggregation) – это отношение типа “часть целого”. Точно так же можно сказать, что двигатель и колеса представляют собой части автомобиля.

One of the most frequent sources of confusion in the UML is aggregation and composition. It’s easy to explain glibly: Aggregation is the part-of relationship. It’s like saying that a car has an engine and wheels as its parts.

- “UML Distilled. A Brief Guide to the Standard Object Modeling Language” 3d edition by Martin Fowler

Агрегация (aggregation) является слабоопределенным типом ассоциации в UML и отображает самую общую зависимость типа “целое-часть” (как и многие другие ассоциации). Этот тип связи не имеет четко определенной семантики в UML, в отличие от обычной ассоциации. Несмотря на это данный термин введен в спецификации UML. Зачем? Ответ заключен в цитате Румбаха (одного из основоположников и ключевых разработчиков UML).

“Несмотря на слабовыраженную семантику агрегации, все считают, что она необходима (по различным причинам). Думайте о ней как о плацебо в моделировании.” - Rumbaugh, J, Jacobson, I., and Booch, G. The Unified Modeling Language Reference Manual, 2e. Reading, MA.: Addison-Wesley, 2004.

Рекомендация. Таким образом, следуя совету создателей UML, не следует использовать агрегацию при моделировании. Вместо этого по возможности стоит использовать композицию (composition).

Агрегация (Aggregation) - Свойство ассоциации, представляющей отношение “целое-часть”, и (обычно) ограничение времени жизни частей временем жизни целого.

Aggregation is a vague kind of association in the UML that loosely suggests whole-part relationships (as do many ordinary associations). It has no meaningful distinct semantics in the UML versus a plain association, but the term is defined in the UML. Why? To quote Rumbaugh (one of the original and key UML creators):

“In spite of the few semantics attached to aggregation, everybody thinks it is necessary (for different reasons). Think of it as a modeling placebo.” - Rumbaugh, J, Jacobson, I., and Booch, G. The Unified Modeling Language Reference Manual, 2e. Reading, MA.: Addison-Wesley, 2004.

Guideline: Therefore, following the advice of UML creators, don’t bother to use aggregation in the UML; rather, use composition when appropriate.

Aggregation - A property of an association representing a whole-part relationship and (usually) life-time containment.

- “Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development” [13] by Craig Larman

Композиция в UML

Наряду с агрегацией в языке UML есть более определенное свойство – композиция (composition). Экземпляр класса Point (Точка) может быть частью многоугольника, а может представлять центр окружности, но он не может быть и тем и другим одновременно. Главное правило состоит в том, что хотя класс может быть частью нескольких других классов, но любой экземпляр может принадлежать только одному владельцу. На диаграмме классов можно показать несколько классов потенциальных владельцев, но у любого экземпляра класса есть только один объектвладелец.

As well as aggregation, the UML has the more defined property of composition. An instance of Point may be part of a polygon or may be the center of a circle, but it cannot be both. The general rule is that, although a class may be a component of many other classes, any instance must be a component of only one owner. The class diagram may show multiple classes of potential owners, but any instance has only a single object as its owner.

- “UML Distilled. A Brief Guide to the Standard Object Modeling Language” 3d edition by Martin Fowler

Композиция – это хороший способ показать свойства, которыми владеют по значению, свойства объектов-значений или свойства, которые имеют определенные и до некоторой степени исключительные права владения другими компонентами. Агрегация совершенно не имеет смысла; поэтому я не рекомендовал бы применять ее в диаграммах. Если вы встретите ее в диаграммах других разработчиков, то вам придется покопаться, чтобы понять их значение. Разные авторы и команды разработчиков используют их в совершенно разных целях.

Composition is a good way of showing properties that own by value, properties to value objects, or properties that have a strong and somewhat exclusive ownership of particular other components. Aggregation is strictly meaningless; as a result, I recommend that you ignore it in your own diagrams. If you see it in other people’s diagrams, you’ll need to dig deeper to find out what they mean by it. Different authors and teams use it for very different purposes.

- “UML Distilled. A Brief Guide to the Standard Object Modeling Language” 3d edition by Martin Fowler

Композиция, так же известная как композитная агрегация (composite aggregation) является строго определенным типом связи “целое-часть” и полезна в некоторых моделях. Отношение композиции предполагает, что 1) экземпляр части (например Square) в каждый момент времени принадлежит только одному целому (например, Board); 2) часть всегда принадлежит целому (пальцы не существуют отдельно от руки); 3) целое ответственно за создание и удаление своих частей — либо через самостоятельное создание/удаление, либо через взаимодействие с другими объектами. Следствием этих ограничений является то, что при уничтожении композитного объекта его части должны быть либо уничтожены, либо присоединены к другому композитному объекту. Например, если реальная настольная игра “Монополия” уничтожается, то также уничтожаются и все ее клетки (с концептуальной точки зрения). Аналогично, если программный объект Board уничтожается, то уничтожаются и программные объекты Square. Для обозначения композиции в UML используется закрашенный ромб на линииассоциации со стороны целого.

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

Composition, also known as composite aggregation, is a strong kind of whole-part aggregation and is useful to show in some models. A composition relationship implies that 1) an instance of the part (such as a Square) belongs to only one composite instance (such as one Board) at a time, 2) the part must always belong to a composite (no free-floating Fingers), and 3) the composite is responsible for the creation and deletion of its partseither by itself creating/deleting the parts, or by collaborating with other objects. Related to this constraint is that if the composite is destroyed, its parts must either be destroyed, or attached to another compositeno free-floating Fingers allowed! For example, if a physical paper Monopoly game board is destroyed, we think of the squares as being destroyed as well (a conceptual perspective). Likewise, if a software Board object is destroyed, its software Square objects are destroyed, in a DCD software perspective. The UML notation for composition is a filled diamond on an association line, at the composite end of the line.

Composition - The definition of a class in which each instance is comprised of other objects.

- “Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development” [13] by Craig Larman

Агрегат в DDD

Итак, то что в UML называется “композиция” или “агрегация”, в DDD называется просто “агрегация”.

AGGREGATE - A cluster of associated objects that are treated as a unit for the purpose of data changes. External references are restricted to one member of the AGGREGATE , designated as the root. A set of consistency rules applies within the AGGREGATE’S boundaries.

- “Domain-Driven Design: Tackling Complexity in the Heart of Software” [5] by Eric Evans

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

What is an Aggregate? Two are represented here. Each Aggregate is composed of one or more Entities, where one Entity is called the Aggregate Root. Aggregates may also have Value Objects composed on them. As you see here, Value Objects are used inside both Aggregates.

The Root Entity of each Aggregate owns all the other elements clustered inside it. The name of the Root Entity is the Aggregate’s conceptual name. You should choose a name that properly describes the conceptual whole that the Aggregate models.

Each Aggregate forms a transactional consistency boundary. This means that within a single Aggregate, all composed parts must be consistent, according to business rules, when the controlling transaction is committed to the database. This doesn’t necessarily mean that you are not supposed to compose other elements within an Aggregate that don’t need to be consistent after a transaction. After all, an Aggregate also models a conceptual whole. But you should be first and foremost concerned with transactional consistency. The outer boundary drawn around Aggregate Type 1 and Aggregate Type 2 represents a separate transaction that will be in control of atomically persisting each object cluster.

The reasons for the transactional boundary are business motivated, because it is the business that determines what a valid state of the cluster should be at any given time. In other words, if the Aggregate was not stored in a whole and valid state, the business operation that was performed would be considered incorrect according to business rules.

- “Domain-Driven Design Distilled” by Vaughn Vernon

Виды связей по способу доступа

Объектные Ссылки (Object References)

Этот способ связи экземпляров Доменных Моделей применялся достаточно давно. Часто его называют “Eager Loading”. Со временем он уступил свои позиции Ленивой Загрузке, но, по мере роста применяемости распределенных систем, и, соответственно, по мере роста интереса к понятию Агрегата (как границы транзакции), интерес к нему снова возвратился.

Также этот способ часто применялся в асинхронных приложениях (до появления конструкции async/await), поскольку асинхронное обращение к ленивым связям Доменных Моделей значительно усложняет ясность программного кода, и препятствует оптимизации и пакетной предзагрузке связанных объектов.

Классическая реализация создания объектных ссылок

Чтобы построить объектные ссылки, Data Mapper извлекаемой Domain Model обращается к Data Mapper связанной Domain Model и линкует связанные экземпляры Доменных Моделей при их создании.

Наглядную реализацию можно посмотреть в “Chapter 12. Object-Relational Structural Patterns :: Foreign Key Mapping” книги “Patterns of Enterprise Application Architecture” by Martin Fowler, David Rice, Matthew Foemmel, Edward Hieatt, Robert Mee, Randy Stafford. Смотрите Example: Single-Valued Reference (Java): Figure 12.4. Sequence for loading a single-valued field и реализацию класса AlbumMapper. А так же Example: Multitable Find (Java).

Другим хорошим примером может служить легковесный Data Mapper OpenORM.

Этот принцип реализован также и в библиотеке js-data.

Классическая реализация создания агрегатов

Для построения агрегатов часто используется паттерн “Dependent Mapping”, смотрите “Chapter 12. Object-Relational Structural Patterns :: Dependent Mapping” книги “Patterns of Enterprise Application Architecture” by Martin Fowler, David Rice, Matthew Foemmel, Edward Hieatt, Robert Mee, Randy Stafford. В этом случае один DataMapper ответственен за создание всего агрегата.

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

The basic idea behind Dependent Mapping is that one class (the dependent) relies upon some other class (the owner) for its database persistence. Each dependent can have only one owner and must have one owner.

- “Patterns of Enterprise Application Architecture” by Martin Fowler, David Rice, Matthew Foemmel, Edward Hieatt, Robert Mee, Randy Stafford

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

A dependent may itself be the owner of another dependent. In this case the owner of the first dependent is also responsible for the persistence of the second dependent. You can have a whole hierarchy of dependents controlled by a single primary owner.

- “Patterns of Enterprise Application Architecture” by Martin Fowler, David Rice, Matthew Foemmel, Edward Hieatt, Robert Mee, Randy Stafford

Создание Объектных Ссылок на уровне инфраструктуры

Для сохранения агрегата целиком часто используются агрегато-ориентированные NoSQL хранилища данных, Application Database и REST-API endpoints.

Создание Объектных Ссылок на уровне приложения

В DDD связка агрегатов между собой может производиться на уровне логики приложения, например, в обработчике CQRS Command Handler. Дело в том, что слой Application Logic (или слой Application-specific Business Rules), координирующий работу слоя Business Rules, обладает более лучшими возможностями для предвидения, оптимизации и пакетной загрузки требуемых данных с удаленных источников. А это играет немаловажную роль, когда накладные расходы получения данных с удаленных источников высоки.

Objects within the AGGREGATE can hold references to other AGGREGATE roots.

- “Domain-Driven Design: Tackling Complexity in the Heart of Software” [5] by Eric Evans

Поскольку один экземпляр АГРЕГАТА может ссылаться на другие экземпляры АГРЕГАТА, можно ли перемещаться по связям достаточно далеко, изменяя различные объекты на пути следования?

Since one Aggregate instance can reference other Aggregate instances, can the associations be navigated deeply, modifying various objects along the way?

- “Implementing Domain-Driven Design” [6] by Vaughn Vernon

При этом нужно отметить, что:

  1. И АГРЕГАТ (Backlogitem), который ссылается, и АГРЕГАТ (Product), на который ссылаются, не должны. изменяться в рамках одной и той же транзакции. В отдельно взятой транзакции может изменяться только один из них.
  2. Если вы изменяете несколько экземпляров в рамках одной транзакции, значит, ваши границы согласованности проведены неправильно. В этом случае, вероятно, в ходе моделирования вы пропустили какую-то концепцию; некая концепция ЕДИНОГО ЯЗЫКА еще не выявлена, хотя она бросается в глаза (такая ситуация была описана выше).
  3. Если вы пытаетесь применить п. 2 и получаете крупнокластерный АГРЕГАТ со всеми его недостатками, указанными ранее, возможно, вам следует использовать концепцию итоговой, а не атомарной согласованности (см. ниже).
  1. Both the referencing Aggregate ( BacklogItem ) and the referenced Aggregate ( Product ) must not be modified in the same transaction. Only one or the other may be modified in a single transaction.
  2. If you are modifying multiple instances in a single transaction, it may be a strong indication that your consistency boundaries are wrong. If so, it is possibly a missed modeling opportunity; a concept of your Ubiquitous Language has not yet been discovered although it is waving its hands and shouting at you (see earlier in this chapter).
  3. If you are attempting to apply point 2, and doing so influences a large-cluster Aggregate with all the previously stated caveats, it may be an indication that you need to use eventual consistency (see later in this chapter) instead of atomic consistency.

- “Implementing Domain-Driven Design” [6] by Vaughn Vernon

При разработке АГРЕГАТОВ может возникнуть желание создать структуру, допускающую обход по глубоким графам объектов, но назначение этого шаблона другое. В книге [Эванс] утверждается, что один АГРЕГАТ может содержать ссылки на КОРНИ других АГРЕГАТОВ. Однако мы должны иметь в виду, что это не помещает АГРЕГАТ, на который ссылаются, в границы согласованности АГРЕГАТА, который на него ссылается. Ссылка не порождает один целостный АГРЕГАТ. Вместо него существуют два (или больше) АГРЕГАТА, как показано в рис. 10.5.

When designing Aggregates, we may desire a compositional structure that allows for traversal through deep object graphs, but that is not the motivation of the pattern. [Evans] states that one Aggregate may hold references to the Root of other Aggregates. However, we must keep in mind that this does not place the referenced Aggregate inside the consistency boundary of the one referencing it. The reference does not cause the formation of just one whole Aggregate. There are still two (or more), as shown in Figure 10.5.

- “Implementing Domain-Driven Design” [6] by Vaughn Vernon

Ссылки по идентификатору не могут полностью исключить навигацию по модели. Для выполнения поиска к ХРАНИЛИЩУ (12) иногда обращаются из АГРЕГАТА. Этот способ называется ОТКЛЮЧЕННОЙ МОДЕЛЬЮ ПРЕДМЕТНОЙ ОБЛАСТИ (DISCONNECТED DOМAIN МODEL). По существу, он представляет собой одну из форм отложенной загрузки. Впрочем, существуют разные подходы к поиску зависимых объектов с помощью АГРЕГАТОВ: использовать ХРАНИЛИЩЕ или СЛУЖБУ ПРЕДМЕТНОЙ ОБЛАСТИ (7). Этой службой может управлять клиент ПРИКЛАДНОЙ СЛУЖБЫ, который потом передает управление АГРЕГАТУ.

Наличие ПРИКЛАДНОЙ СЛУЖБЫ делает АГРЕГАТ независимым от РЕПОЗИТАРИЯ или СЛУЖБЫ ПРЕДМЕТНОЙ ОБЛАСТИ. Однако в очень сложных и проблемно-ориентированных системах для освобождения от зависимости, возможно, лучше всего передать СЛУЖБУ ПРЕДМЕТНОЙ ОБЛАСТИ в командный метод АГРЕГАТА. Тогда АГРЕГАТ сможет выполнить двойную диспетчеризацию, обращаясь к СЛУЖБЕ ПРЕДМЕТНОЙ ОБЛАСТИ, и разрешить ссылки. Повторим, каким бы способом один АГРЕГАТ ни получал доступ к другим АГРЕГАТАМ, ссылка на многочисленные АГРЕГАТЫ в одном запросе не дает оснований для модификации нескольких агрегатов.

Ограничение модели использовать исключительно ссылки с помощью идентификаторов затрудняет обслуживание клиентов, собирающих и прорисовывающих представления ПОЛЬЗОВАТЕЛЬСКОГО ИНТЕРФЕЙСА (14). Для рассылки представлений в отдельном сценарии использования, вероятно, придется использовать многочисленные ХРАНИЛИЩА. Если затраты, связанные с выполнением запросов, снижают производительность системы, то, может быть, целесообразно рассмотреть возможность использования тета-обьединений (theta joins) или принципа CQRS. Библиотека Hibernate, например, использует тета-объединения как средство сборки многих связанных ссылками АГРЕГАТОВ в один объединенный запрос, который может обеспечить просмотр необходимых частей. Если принцип CQRS и тета-объединения не подходят, вы, возможно, должны достичь баланса между косвенными и прямыми ссылками.

Reference by identity doesn’t completely prevent navigation through the model. Some will use a Repository (12) from inside an Aggregate for lookup. This technique is called Disconnected Domain Model, and it’s actually a form of lazy loading. There’s a different recommended approach, however: Use a Repository or Domain Service (7) to look up dependent objects ahead of invoking the Aggregate behavior. A client Application Service may control this, then dispatch to the Aggregate.

Having an Application Service resolve dependencies frees the Aggregate from relying on either a Repository or a Domain Service. However, for very complex and domain-specific dependency resolutions, passing a Domain Service into an Aggregate command method can be the best way to go. The Aggregate can then double-dispatch to the Domain Service to resolve references. Again, in whatever way one Aggregate gains access to others, referencing multiple Aggregates in one request does not give license to cause modification on two or more of them.

Limiting a model to using only reference by identity could make it more difficult to serve clients that assemble and render User Interface (14) views. You may have to use multiple Repositories in a single use case to populate views. If query overhead causes performance issues, it may be worth considering the use of theta joins or CQRS. Hibernate, for example, supports theta joins as a means to assemble a number of referentially associated Aggregate instances in a single join query, which can provide the necessary viewable parts. If CQRS and theta joins are not an option, you may need to strike a balance between inferred and direct object reference.

- “Implementing Domain-Driven Design” [6] by Vaughn Vernon

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

Ленивая Загрузка (Lazy Load)

Ленивая Загрузка (Lazy Load) бывает синхронной и асинхронной.

Классическая реализация с использованием IoC

Ключевым моментом при реализации Ленивой Загрузки связей является освобождение Domain Model от логики доступа к источнику данных. Это необходимо как из принципа чистоты архитектуры и проектных решений, чтобы снизить сопряжение (Coupling), так и из принципа простоты тестирования.

Классический вариант реализации синхронной Ленивой Загрузки связанных объектов, основанный на IoC, можно увидеть в подразделе “Example: Separating the Finders (Java)” главы “Chapter 10. Data Source Architectural Patterns :: Data Mapper” книги “Patterns of Enterprise Application Architecture” [4].

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

В данном примере использовался паттерн Registry для внедрения реализации интерфейса ArtistFinder (см. класс MapperRegistry), что добавляет Доменной Модели еще и осведомленность об интерфейсе Registry, который представлен, к тому же, еще и глобальным объектом.

Впрочем, последнюю осведомленность можно избежать, если использовать пассивное внедрение зависимостей.

Для этого часто используется Каррирование фабричной функции создания экземпляра Доменной Модели. Идея с внедрением зависимости через замыкания пришла из функционального программирования (преобразование более общей функции в более конкретную функцию). Этот метод хорошо работает, например, в Python, где нет синтаксического различия между инстанционированием экземпляра класса и вызовом обычной функции.

В некоторых проектах я встречал внедрение зависимостей посредством использования замыкания фабрики класса Доменной Модели, а не фабрики экземпляра Доменной Модели.

Реализация посредством AOP (Cross-Cutting Concerns)

В отличии от только что рассмотренного варианта классической реализации, использующей IoC, реализация посредством AOP (Cross-Cutting Concerns) позволяет полностью освободить Доменную Модель от осведомленности об интерфейсе доступа к данным. Доменная Модель просто объявляет методы, ответственные за доступ к связанным объектам, но не реализует их.

Когда Repository создает экземпляр доменной модели, он накладывает на этот экземпляр аспект, ответственный за доступ к данным и реализующий все методы, которые были объявлены, но не реализованы, Доменной Моделью. В этом случае, объект-обертка, реализующий интерфейс оригинального объекта, обычно называется Aspect или Decorator (см. одноименный паттерн в GOF). Часто в таких случаях можно услышать термин Proxy, но, на самом деле паттерн Proxy имеет немного другое назначение.

Более подробно о Cross-Cutting Concerns можно посмотреть в главах:

  • “Chapter 11: Systems :: Cross-Cutting Concerns”
  • “Chapter 11: Systems :: Pure Java AOP Frameworks”

книги “Clean Code: A Handbook of Agile Software Craftsmanship” [1] by Robert C. Martin.

If the framework wants you to derive your business objects from its base classes, say no! Derive proxies instead, and keep those proxies in components that are plugins to your business rules.

- “Clean Architecture: A Craftsman’s Guide to Software Structure and Design” [2] by Robert C. Martin

Эмуляция Объектных Ссылок (Object References)

Для того, чтобы Ленивые Загрузки со стороны клиента выглядели как Объектные Ссылки, часто применяются Descriptors, которые, зачастую, динамически внедряются в класс Доменной Модели в момент инициализации класса посредством метапрограммирования.

Оптимизация и пакетная загрузка

Недостатком Ленивой Загрузки является N+1 проблема, которая заключается в большом количество запросов к хранилищу данных. С целью оптимизации, ленивые связи можно предварительно загрузить пакетно.

Как реализует связи библиотека?

Наша библиотека позволяет реализовать связи как посредством Объектных Ссылок, так и посредством Ленивой Загрузки, включая Cross-Cutting Concerns. Причем, однажды созданные Объектные Ссылки автоматически реагируют (актуализируются) в ответ на изменения состояния Repository (что особенно актуально для One-To-Many связей). Реакции реализованы как в парадигме Реактивного программирования, так и в парадигме Событийно-ориентированного программирования (на выбор). Объектные Ссылки могут создаваться как односторонние, так и двусторонние.

Исходный код

This article in English “Implementation of Repository pattern for browser’s JavaScript”.

Footnotes

[1](1, 2)Clean Code: A Handbook of Agile Software Craftsmanship” by Robert C. Martin
[2]“Clean Architecture: A Craftsman’s Guide to Software Structure and Design” by Robert C. Martin
[3]Code Complete” Steve McConnell
[4]Patterns of Enterprise Application Architecture” by Martin Fowler, David Rice, Matthew Foemmel, Edward Hieatt, Robert Mee, Randy Stafford
[5](1, 2, 3, 4, 5, 6, 7) “Domain-Driven Design: Tackling Complexity in the Heart of Software” by Eric Evans
[6](1, 2, 3, 4) “Implementing Domain-Driven Design” by Vaughn Vernon
[7]“Design Patterns Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, 1994
[8]Refactoring: Improving the Design of Existing Code” by Martin Fowler, Kent Beck, John Brant, William Opdyke, Don Roberts
[9]OO vs FP” by Robert C. Martin
[10]Clean Architecture” by Robert C. Martin
[11]The Clean Architecture” by Robert C. Martin
[12](1, 2)Extreme Programming Explained” by Kent Beck
[13](1, 2, 3) “Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development” by Craig Larman

Updated on Nov 16, 2019

Comments

comments powered by Disqus