Implementation of Repository pattern for browser’s JavaScript

Good architecture makes you free from certain implementation. It allows you to postpone the moment of decision on implementation and begin code construction even without the decision. The most important point is that you gain the opportunity to make a decision at the time of the greatest awareness, and you can also easily replace a specific implementation with any other. This responsibility is assigned to the Repository.

Thus, you have a complete abstraction from the data source, whether it’s REST-API, MBaaS, SaaS, IndexedDB, HTML, third-party service for JSON-RPC protocol or 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 [11]

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

- Martin Fowler

In addition, you have the opportunity to implement patterns Identity Map and Unit of Work. The last one is very often in demand, since it allows you to roll back the state of local objects in case the data can not be saved (the user has changed his mind or entered invalid data).

Domain Model

The greatest advantage of the Domain Model in the program is the ability to use the principles of Domain-Driven Design (DDD) [4]. If the Models contain only business logic, and are devoid of service logic, then they can easily be read by domain expert (ie, the customer’s representative). This frees you from the need to create UML diagrams for discussions and allows you to achieve the highest level of mutual understanding, productivity, and quality of implementation of the models.

In one project I tried to implement a fairly complex domain logic (which contained more than 30 interrelated domain model) in the paradigm of reactive programming with push-algorithm, when the attributes of the model instance, containing the aggregation annotations or dependent on them, change its values by reacting to changes in other models and storages. The bottom line is that all this reactive logic no longer belonged to the domain model itself, and was located in a different sort of Observers and handlers.

“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” [7]

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

- Kent Beck [11]

“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” [4]

This led to such a huge number of intricacies of listeners that the superiority in performance was lost, but before that was lost readability. Even I could not understand the next day what a particular code fragment does, I’m not talking about the domain expert. This radically destroyed the principles of Domain-Driven Design, and significantly reduced the speed of developing new project features.

Hopes for this approach finally collapsed when it was revealed that each instance of the model is to change the values of its attributes that contain aggregate annotations or dependent upon, depending on the context of use (display selected group or filter criteria).

Subsequently, the models recovered their conceptual outlines and code readability, the push algorithm was replaced by a pull-algorithm (to be more precisely, a hybrid push-pull), and at the same time there was preserved the mechanism of reactions on adding, changing or deleting objects. To achieve this result, I had to create my own library implementing the Repository pattern, since I could not find existing solutions for relational data with quality code base. This is similar to Object-Relational Mapping (ORM) for JavaScript, including the Data Mapper pattern (the data can be mapped between objects and a persistent data storage).

Reactive programming paradigm

Today it is fashionable to get involved in reactive programming. Did you know that dojo developers first applied reactive programming in their implementation of the Repository pattern as early as September 13, 2010?

Reactive programming complements (rather than contrasts) the Repository pattern, as it’s evidenced by the experience of dojo.store and Dstore.

The developers of dojo are a team of highly qualified specialists whose libraries are used by such reputable companies as IBM. An example of how seriously and comprehensively they solves problems is the history of the RequireJS library.

Examples of implementations of Repository pattern and ORM by JavaScript

Examples of the simplest implementations of the Repository pattern by JavaScript in the project todomvc.com:

Other implementations:

  • 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.

I can not mention Ember.js here, because it implements the ActiveRecord pattern.

It is worth also to mention the library rql, which makes it easy the implementing of the patterns Service Stub and Repository. A lot of implementetions you can find in the projects of persvr and kriszyp.

The current article does not consider examples of implementation of the patterns Event Sourcing and CQRS (which are discussed at the article “Роль сервисного слоя в CQRS и Event Sourcing на примере использования Redux в Angular”):

These patterns are used for distributed computing and for systems recreating different states of the system, but their advantages at the frontend are not so obvious, especially given the fact that the implementation of business logic can be most demanded namely at the frontend.

We should also mention the implementation of reactive storage based on the state and using RxJS libraries, see, for example, angular2-rxjs-chat.

Implementation of relationship

Synchronous programming

At the dawn of ORM, the Data Mappers retrieved from the database all related objects with a single query (see example of implementation).

Domain-Driven Design approaches relationships more strictly, and considers relationships from the point of view of conceptual contour of an aggregate of nested objects [4]. The object can be accessed either by reference (from a parent object to an embedded object or to another aggregate root) or through the Repository (to an aggregate root). It is also important the direction of relationships and the principle of minimal sufficiency (“distillation of models” [4]).

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” [4]

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” [4]

With the advent of ORM, lazy evaluation actively began to use to resolve ties synchronous programming. Python community actifely uses Descriptors for this purpose, but Java - AOP and Cross-Cutting Concerns [1].

The key is to free the Domain Model from the data access logic. This is required by the principle of clean architecture to reduce coupling (Coupling), and by the principle of simplicity of testing. The greatest success is achieved by the principle of Cross-Cutting Concerns which completely frees the model from the service logic.

With the advent of ORM the implementation of relationships has become so easy that no one longer think about it. Where unidirectional relationships are required, developers can easily apply bidirectional relationships. Utilities for optimizing the selection of related objects have appeared, which implicitly preload all related objects, which significantly reduces the number of calls to the database.

Asynchronous programming

The rise in popularity of asynchronous applications has forced us to reconsider the established notions about the implementation of lazy relationships. Asynchronous access to each lazy relationship of each object significantly complicates the clarity of the program code and prevents optimization.

This facilitated the popularity of object-oriented database in asynchronous programming that allows to save aggregates entirely. Increasingly, REST-frameworks began to be used to transfer aggregates of nested objects to the client.

To do anything with an object, you have to hold a reference to it. How do you get that reference? One way is to create the object, as the creation operation will return a reference to the new object. A second way is to traverse an association. You start with an object you already know and ask it for an associated object. Any object-oriented program is going to do a lot of this, and these links give object models much of their expressive power. But you have to get that first object.

I actually encountered a project once in which the team was attempting, in an enthusiastic embrace of MODEL-DRIVEN DESIGN , to do all object access by creation or traversal! Their objects resided in an object database, and they reasoned that existing conceptual relationships would provide all necessary associations. They needed only to analyze them enough, making their entire domain model cohesive. This self-imposed limitation forced them to create just the kind of endless tangle that we have been trying to avert over the last few chapters, with careful implementation of ENTITIES and application of AGGREGATES . The team members didn’t stick with this strategy long, but they never replaced it with another coherent approach. They cobbled together ad hoc solutions and became less ambitious.

Few would even think of this approach, much less be tempted by it, because they store most oftheir objects in relational databases. This storage technology makes it natural to use the third way of getting a reference: Execute a query to find the object in a database based on its attributes, or find the constituents of an object and then reconstitute it.

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

The need of processing nested data structures has intensified interest in functional programming, especially in combination with reactive programming paradigm.

Implementation of relationships by assigning

Although an aggregate is not compatible with Many-To-Many relationships and cross-link hierarchies, it can still refer to the root of another aggregate:

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

- “Domain-Driven Design: Tackling Complexity in the Heart of Software” [4] 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” [5] by Vaughn Vernon

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” [5] by Vaughn Vernon

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.

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

The principle of physical assignment of related objects is implemented also by the library js-data.

In our library, we implemented both the ability to decompose nested objects and the ability to compose dested objects from flat lists of data in Repositories. Moreover, selected collections always keeps the actual state. When you add, change, delete an object in the Repository, the changes automatically propagate to the selected collections. The library implements this behavior as in the paradigm of reactive programming, as well as in the paradigm of event-driven programming (optional).

There is also the ability to create bidirectional relationships. But, despite the fact that modern interpreters able to easily collect garbage with reference cycles, it’s better when child objects are not aware of their parents from a conceptual point of view, if you don’t have a strong reason for that.

Thus, the implementation of communications does not require any service data access logic for the object, that provides zero Coupling and absolutly clear domain models. This means that domain model can be instance of the “class” Object.

Source code

Эта статья на Русском языке “Реализация паттерна Repository в браузерном JavaScript”.

Footnotes

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

Updated on May 26, 2018

Comments

comments powered by Disqus