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 [10])
“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 save only changed objects of the finally formed aggregate of nested objects on the server, or 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» [6])
“Good design puts the logic near the data it operates on.” (Kent Beck [10])

“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, Dstore and the new Dojo 2 - data stores.

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:

I would like to add here Ember.js, but it implements the ActiveRecord pattern.

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 the parent object to the embedded object) or through the Repository. 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.

Rejecting relationships

It is worth mentioning another widespread point of view, which says that an object should not be responsible for its relationships, and only Repository can have an exclusive right to access the object. Some respected by me friends adhere to this point of view.

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 has increased 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 for processing aggregates has intensified interest in functional programming, especially in combination with reactive programming paradigm.

However, the solution to one problem creates another problem.

Functional Programming

Functional programming is more difficult to use for domain objects, since it is more difficult to structure logically (especially if programming language does not support multiple dispatching). This often leads to unreadable code that expresses not “what” it does, but “how” it does something incomprehensible.

If you wanted polymophism in C, you’d have to manage those pointers yourself; and that’s hard. If you wanted polymorphism in Lisp you’d have to manage those pointers yourself (pass them in as arguments to some higher level algorithm (which, by the way IS the Strategy pattern.)) But in an OO language, those pointers are managed for you. The language takes care to initialize them, and marshal them, and call all the functions through them.

... There really is only one benefit to Polymorphism; but it’s a big one. It is the inversion of source code and run time dependencies. («OO vs FP» [7])

However, my experience is that the cost of change rises more steeply without objects than with objects. (Kent Beck [10])

And yet, not clear intentions and objectives of the author - is a key issue when reading someone else’s code.

A six-month study conducted by IBM found that maintenance programmers “most often said that understanding the original programmer’s intent was the most difficult problem” (Fjelstad and Hamlen 1979). («Code Complete» [2])

As it mentioned in the article “How to quickly develop high-quality code. Team work.”, the developer reads the code 91% of the time while constructing the code, and only 9% of the time he enters the characters with keyboard. And this means that poorly readable code affects 91% of the development velocity.

Also, this approach destroys all the benefits of using Domain-Driven Design and pull apart the elements implementing the conceptual objects, which leads to the code that no longer expresses the model.

All this contributed to the appearance in the ReactJS community of such libraries as:

  • Normalizr - Normalizes (decomposes) nested JSON according to a schema.
  • Denormalizr - Denormalize data normalized with normalizr.

Minor offtopic

Despite the fact that functional programming techniques are often used together with the paradigm of reactive programming, in their essence these paradigms are not always suitable for combination in the canonical form for web development.

This is because reactive programming is based on the propagation of changes, i.e. it implies the existence of variables and assignment.

This means that it becomes possible to express static (e.g. arrays) or dynamic (e.g. event emitters) data streams with ease via the employed programming language(s), and that an inferred dependency within the associated execution model exists, which facilitates the automatic propagation of the change involved with data flow.

For example, in an imperative programming setting, a := b + c would mean that a is being assigned the result of b + c in the instant the expression is evaluated, and later, the values of b and/or c can be changed with no effect on the value of a. However, in reactive programming, the value of a is automatically updated whenever the values of b and/or c change; without the program having to re-execute the sentence a := b + c to determine the presently assigned value of a.

... For example, in an model–view–controller (MVC) architecture, reactive programming can facilitate changes in an underlying model that automatically are reflected in an associated view, and contrarily. (“Reactive programming”, wikipedia)

That is why reactive programming paradigm can be combined with different paradigms, imperative, object-oriented and functional.

However, the whole point of the matter is that in the canonical form of functional programming does not has variables (from the word “vary”), i.e. changeable state:

A true functional programming language has no assignment operator. You cannot change the state of a variable. Indeed, the word “variable” is a misnomer in a functional language because you cannot vary them.

...The overriding difference between a functional language and a non-functional language is that functional languages don’t have assignment statements.

... The point is that a functional language imposes some kind of ceremony or discipline on changes of state. You have to jump through the right hoops in order to do it.

And so, for the most part, you don’t. («OO vs FP» [7])

Therefore, the use of functional programming techniques does not make the program functional until the program has a variable state - it’s just procedural programming. And if so, then the rejection of Domain-Driven Design just takes away the superiority of both approaches (neither object-oriented programming polymorphism nor the immutability of functional programming), and combines the all worst, similar to hybrid objects [1], and does not make program really functional.

Hybrids

This confusion sometimes leads to unfortunate hybrid structures that are half object and half data structure. They have functions that do significant things, and they also have either public variables or public accessors and mutators that, for all intents and purposes, make the private variables public, tempting other external functions to use those variables the way a procedural program would use a data structure (this is sometimes called Feature Envy from “Refactoring” [6]). Such hybrids make it hard to add new functions but also make it hard to add new data structures. They are the worst of both worlds. Avoid creating them. They are indicative of a muddled design whose authors are unsure of—or worse, ignorant of—whether they need protection from functions or types. («Clean Code: A Handbook of Agile Software Craftsmanship» [1])

Canonical functional programming has no state and therefore ideally suited for distributed computing and data flow processing.

The benefit of not using assignment statements should be obvious. You can’t have concurrent update problems if you never update anything.

Since functional programming languages do not have assignment statements, programs written in those languages don’t change the state of very many variables. Mutation is reserved for very specific sections of the system that can tolerate the high ceremony required. Those sections are inherently safe from multiple threads and multiple cores.

The bottom line is that functional programs are much safer in multiprocessing and multiprocessor environments. («OO vs FP» [7])

Does this mean that the object-oriented programming paradigm is opposed to the functional programming programming?

Despite the fact that the OOP paradigm is traditionally considered as a kind of imperative paradigm, i.e. based on the state of the program, Robert C. Martin makes an amazing conclusion - as objects provide their interface, i.e. behavior, and hide their state, they do not contradict the functional programming paradigm.

“Objects are not data structures. Objects may use data structures; but the manner in which those data structures are used or contained is hidden. This is why data fields are private. From the outside looking in you cannot see any state. All you can see are functions. Therefore Objects are about functions not about state.” («OO vs FP» [7])

That’s why some classical functional programming languages support OOP:

  • Enhanced Implementation of Emacs Interpreted Objects

  • Common Lisp Object System

    Are these two disciplines mutually exclusive? Can you have a language that imposes discipline on both assignment and pointers to functions? Of course you can. These two things don’t have anything to do with each other. And that means that OO and FP are not mutually exclusive at all. It means that you can write OO-Functional programs.

    It also means that all the design principles, and design patterns, used by OO programmers can be used by functional programmers if they care to accept the discipline that OO imposes on their pointers to functions. («OO vs FP» [7])

Of course, objects in functional programming must be immutable.

Objects can be emulated even by functional programming languages using closures, see article “Function As Object” by Martin Fowler. Here you can not ignore the wonderful book “Functional Programming for the Object-Oriented Programmer” by Brian Marick.

Let’s remember the chapter “Chapter 6. Working Classes: 6.1. Class Foundations: Abstract Data Types (ADTs): Handling Multiple Instances of Data with ADTs in Non-Object-Oriented Environments” книги «Code Complete» [2].

An abstract data type is a collection of data and operations that work on that data. («Code Complete» [2])
Abstract data types form the foundation for the concept of classes. («Code Complete» [2])
Thinking about ADTs first and classes second is an example of programming into a language vs. programming in one. («Code Complete» [2])

I’m not here to rewrite all the advantages of ADT, you can read it in this chapter of this book.

But the original question was whether we should abandon the ADT in an object-oriented language for the design of domain objects in favor of “Anemic Domain Model”? And should we sacrifice all the benefits of Domain-Driven Design for the sake of the convenience of a particular implementation of relationship resolving?

Object-oriented model of polymorphism does an important thing - dependency injection. With the abandonment of the object-oriented model, the issue of dependency injection remains open.

The bottom, bottom line here is simply this. OO programming is good, when you know what it is. Functional programming is good when you know what it is. And functional OO programming is also good once you know what it is. («OO vs FP» [7])

It is also worth noting that not all kinds of relationships fit into the concept of aggregate. If the object does not logically belong to the aggregate, then we can not put it into the aggregate for the sake of the convenience of resolving the relationships. For in this case, the interface will follow the implementation, which fundamentally destroys the fundamental principle of abstraction. Also, the concept of an aggregate can not be used to emulate Many-To-Many relationships and cross-link hierarchies.

Implementation of relationships by assigning

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 aggregates of nested objects and the ability to compose aggregates from flat data of Repositories. Moreover, the aggregate always keeps the actual state. When you add, change, delete an object in the Repository, the changes automatically propagate to the structures of the corresponding aggregates. 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.

I also took into account the point of view that the domain model should not be responsible for the relationships. Therefore, there is the possibility of easy access to any object through its Repository.

Source code

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

Footnotes

[1](1, 2, 3, 4) «Clean Code: A Handbook of Agile Software Craftsmanship» by Robert C. Martin
[2](1, 2, 3, 4, 5) «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) «Domain-Driven Design: Tackling Complexity in the Heart of Software» by Eric Evans
[5]«Design Patterns Elements of Reusable Object-Oriented Software» by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, 1994
[6](1, 2) «Refactoring: Improving the Design of Existing Code» by Martin Fowler, Kent Beck, John Brant, William Opdyke, Don Roberts
[7](1, 2, 3, 4, 5, 6) «OO vs FP» by Robert C. Martin
[8]«Clean Architecture» by Robert C. Martin
[9]«The Clean Architecture» by Robert C. Martin
[10](1, 2, 3) «Extreme Programming Explained» by Kent Beck

Updated on Dec 25, 2017

Comments

comments powered by Disqus