DEV Community

Cover image for Pragmatic developer: Learn architecture and design patterns. Using ORM and ActiveRecord
Vitaly Krenel
Vitaly Krenel

Posted on • Edited on

Pragmatic developer: Learn architecture and design patterns. Using ORM and ActiveRecord

Table of content:

Intro

This section is the series introduction. If you want to start with the article immediately, just read last 2 paragraphs to understand the subject and go ahead.

Building a digital product is not easy. There are a lot of distinct challenges involved. Among those are code architecture challenges. As engineers, we strive to write beautiful code. As product creators, we strive to write code that immediately resolves the problem.

Unfortunately, you cannot solely write beautiful code - you will spend so much time polishing one small piece, that the whole sense of creating a product will be lost. Instead of growing a product that eliminates the real problem of your customers, you will focus on discussing all the time whether you need to put a condition outside or inside a function.

At the same time, you cannot write only code that immediately solves the problem - you will be dragged repeatedly into dealing with small annoying bugs, will have to write a huge bunch of boilerplate code just to introduce something and that's mostly because of your inability to foresee and prepare to scale. I won't even say about the amount of effort needed to maintain such a codebase and, what for me is even more important, the feeling you experience working on something you find ugly.

Instead, I believe, that to be an effective developer capable of both creating and maintaining a product, you need to write pragmatic code. Pragmatic, as I see it, means sometimes to build a foundation beforehand. Sometimes to introduce a proper solution only on-demand, when you see an actual problem getting back over and over. Sometimes ditch a proper solution, fix the problem straight away and forget it for the time being. Overall, it's about being reasonable, realistic and not doing what is unnecessary, but still, being an engineer that favors elegance and beauty. No matter how controversial this statement sounds.

And here comes the first trouble. To be a pragmatic developer, you need to know a lot of possible solutions that you can use in specific cases. Otherwise, you'll go with what you have, without any consideration about what would be appropriate in that exact case.

This article is part of the intended series on architecture concepts and design patterns that I'm interested in, have some knowledge or experience with and would like to share so you could be more pragmatic having extra options to choose from.

The content of the series, in my mind, is suitable for developers with 2-3 years of relatively diverse development experience. I will assume you tried both frontend and backend web development to some adequate degree. If you are in the industry only for a year or even less but is "ahead of a crowd", you should still be able to follow along.

I mainly use Typescript-like syntax for code examples as a compromise between having well-expressed code pieces and those that can be digested by devs with different programming language backgrounds. In some cases, it may not be a completely valid code piece with some required parts being stripped.


The first article is focusing on the domain and data persistence. I cover the ORM concept and specifically the wide-spread ActiveRecord pattern as one of the solutions that may be used to keep the persistence layer (the one responsible for storing and retrieving application data) relatively simple and thus maintainable.

As I already typed down a small self-composed lyrical essay, we can finally start with domain field and domain model concepts that are, in some sense, an underlying part of any application's persistence.

What is a domain model?

In any application, we can find some kind of a domain model - a conceptual model of any specific field represented by entities, their behaviors, and relationships between them.

To create a domain model you need to explore the domain field. You do not need to understand and know the whole field in the finest details, it is usually enough to understand a part of the field (basically, some of the terms and concepts used there). Sometimes, you may have a domain expert around which you can consult with. Sometimes, you have to look for information yourself.

Let's consider driver education as an example of the domain field. I assume, that any driving school has several instructors - then, we can say that a driving instructor is a domain entity.

An instructor has a name, gender, a list of licenses they teach, some information about their driving experience.

class DrivingInstructor {
  firstName: string;
  lastName: string;
  gender: string;
  teachableLicenses: License[];
  yearsOfDrivingExperience: number;
}
Enter fullscreen mode Exit fullscreen mode

This DrivingInstructor class represents one specific entity and is a part of our domain model.

You are not obliged to use classes to design the domain, simple literal objects with factory functions are okay. Moreover, this approach is quite convenient in FP or FP-like programming styles, as the paradigm encourages usage of pure functions and immutability instead of classes (which favor mutability instead).

interface DrivingInstructor = {
  firstName: string;
  lastName: string;
  gender: string;
  teachableLicenses: License[];
  yearsOfDrivingExperience: number;
};

const createDrivingInstructor = ({
  firstName,
  lastName,
  gender,
  /* ... */
}): DrivingInstructor => ({
  firstName,
  lastName,
  gender,
  /* ... */
});
Enter fullscreen mode Exit fullscreen mode

There can be many different entities within the domain - basically, any real-life thing, e.g. an employee, banking account, a cup of coffee, article, etc., can be a domain entity. There can be 5 or several dozens of entities - it depends on the developer's approach and the domain field complexity.

To practice a bit further designing the domain model, take a restaurant business. There, you would have a reservation as a domain field concept.

class Reservation {
  id: number;
  date: Date;
  seatingAreaId: number;
  guestsCount: number;
  ownerId: number;
  status: ReservationStatus;
}
Enter fullscreen mode Exit fullscreen mode

In this case, the reservation has relationships with several entities, more specifically "has-a" relationship with Seating Area (probably a table or a room) and belongs to the Reservation Owner i.e. the Owner has a "has-a" relationship with Reservation.

Alt Text

If you are not familiar with "has a" relationship take a look at the "Aggregation" section in this wiki article.

What about behavior? When you book a table in a restaurant, there's usually an option to cancel the reservation in case you have changed your mind (or those, you were planning to go with, did :sigh). So this cancel operation is the behavior available for the Reservation.

class Reservation {
  /* ... */

  void cancel() {
    if (this.status !== ReservationStatus.FINISHED) {
      this.status = ReservationStatus.CANCEL;
    }
  }
Enter fullscreen mode Exit fullscreen mode

Behavior is any business-related logic. Usually, it is a method or a function that changes the state of an entity (like cancel method), calculates something (e.g. .transfer(destinationAccount) method in a banking app) or performs some validations.

We discussed what are the domain and domain entities, but those are only the concepts that start to make sense when they have some actual data packed into them. We still need to discuss how to approach storing that data and restoring it when we want. These concerns are covered by the persistence layer.

Data persistence

In general, to persist the data means to retrieve application data after a program execution has started and to store some data after the program has finished.

In the frontend case, opening a tab with a web app is the start and closing that tab is the end. With backend, it may differ from language to language, but we can assume that receiving an HTTP request is the start and sending the response back (or closing the connection) is the end.

Usually, you want the application data to be available after the execution is ended. That makes the persistence-related code an important part of the codebase. If it fails often or is bug-prone, you may lose the data. Moreover, asking users to do something twice, may easily annoy them and turn away from our products (which is, you know, undesirable from a business perspective).

How would you deal with it: how would you organize the code responsible for persistence? The very basic approach may be to use SQL statements throughout our code each time we need to put to or pull from a database:

const reservation = new Reservation({
  ownerId: 13,
  seatingAreaId: 8,
  guestsCount: 3,
  date: new Date('2019-10-19 17:30'),
  status: ReservationStatus.PENDING,
});

database.prepareQuery(`
  INSERT INTO reservations (
    Id, OwnerId, SeatingAreaId, GuestsCount, Date, Status
  ) VALUES (
    :id, :ownerId, :seatingAreaId, :guestsCount, :date, :status
  )`,
  reservation,
).execute();
Enter fullscreen mode Exit fullscreen mode

This approach is a way to go in small apps or those that are in MVP state, but, you can guess, it is not scalable.

A more appropriate solution to the problem can be to introduce an ORM.

ORM as a layer of abstraction

ORM (Object Relational Mapping) is the concept of mapping existent database records to your application. So instead of performing database operations manually (the same way I did in the example above), you would represent different database operations through different objects and method calls.

ORM is a pattern. But quite often in spoken language when people say "ORM" they mean a specific library (or a tool) that covers database abstraction concern in their language of choice. For example, in PHP there are Eloquent and Doctrine ORMs. In Ruby on Rails - ActiveRecord (it is both the name of the pattern and Rails' ORM). Python has SQLAlchemy and Django ORM.

So to prevent confusion, I suggest you perceive ORM as a layer that abstracts database-related operations and is a bridge between database records and your domain entities classes (that are called "models" in MVC or MV* architecture styles).

ORM may be built with the help of different patterns. And there are a few that may change how you would persist the domain entities. In this article, I focus on the ActiveRecord pattern.

ActiveRecord pattern to persist application data changes

an object that wraps a record in a database table, not only carries both data and behavior but also encapsulates the database access. See ActiveRecord's definition by Martin Fowler

Highlighting the most important from that definition, we can get two main points:
1) your domain objects are mapped to the database records;
2) they [objects] handle the database access themselves.

class ActiveRecord {
  static tableName: string;
  static schema: Schema;
  static isInitialized: Boolean;

  constructor(...entityProperties) {
    this.init();
    this.defineFields();

    for ([propertyName, propertyValue] of Object.pairs(entityProperties)) {
      this[propertyName] = propertyValue;
    }
  }

  /**
    * Find related table in the database and retrieve its schema (meta
    * information) like column names, their types, nullable or not, etc.
    */
  static private init() {
    if (isEmpty(this.tableName)) {
      // Guess table name from the class e.g. 'Reservation' class
      // can be converted to 'reservations' table name
      this.tableName = Database.guessTable(this.constructor.name);
    }

    if (isEmpty(this.schema)) {
      this.retrieveTableSchema();
    }

    this.isInitialized = true;
  }

  /**
    * Retrieve table meta information that exists in the corresponding table
    */
  private retrieveTableSchema() {
    this.schema = Database.retrieveSchema(this.tableName);
  }

  /**
    * Create a new field in the object for each table column,
    * so they can be populated later with real data.
    */
  private defineFields() {
    for (column of this.meta) {
      // Convert column name to field name and initialize a field
      // on the object, e.g. for 'owner_id' column, reservation object
      // will have 'ownerId' field
      const fieldName = convertToCamelCase(column.name);
      this[fieldName] = null;
    }
  }

  /*
   * Persist the entity object to the database
   */
  public save(): void {
    // Covert back from camelCase to syntax that was used in table column names
    // the resulting object can be pushed to the database to an existing
    // record or add a new one
    const tableRecord = mapFieldsToColumns(
      this.fields,
      this.schema,
    );

    Database.insertOrUpdate(this.tableName, tableRecord);
  }

  public delete(): Boolean {
    return Database.delete(this.tableName, this.fields.id);
  }

  public static find(params): any {
    if (this.isInitialized === false) {
      this.init();
    }

    const searchableRecord = mapParamFieldsToColumns(
      this.schema,
      params,
    );

    const record = Database.find(this.tableName, searchableRecord);
    const entityProperties = mapToCamelCase(record);

    // Static methods in JS recieves class itself as 'this' value
    // So this would translate to new Reservation(...entityProperties)
    return new this(...entityProperties);
  }

  /**
    * Count records in the corresponding table
    */
  public static count(): Number { /* ... */ }

  /**
    * Reset the whole table
    */
  public static reset(): Boolean { /* ... */ }
}
Enter fullscreen mode Exit fullscreen mode

Now any domain entity extending this ActiveRecord class will be able to work with the database on its own.

class Reservation extends ActiveRecord {
  // Entity fields are implicitly declared when a new object
  // is created or pulled from the database

  // id: number;
  // date: Date;
  // seatingAreaId: number;
  // guestsCount: number;
  // ownerId: number;
  // status: ReservationStatus;

  confirm(): void {
    this.status = ReservationStatus.CONFIRMED;
  }
}

const reservation = new Reservation({
  reservationOwnerId: 13,
  seatingAreaId: 8,
  guestsCount: 3,
  date: new Date('2019-10-19 17:30'),
  status: ReservationStatus.PENDING
});

// Manager confirmed the reservation, but the guest
// has asked to reschedule the reservation time until 19:30
reservation.confirm();
reservation.date = new Date('2019-10-19 17:30');
reservation.save();
Enter fullscreen mode Exit fullscreen mode

With ActiveRecord, all additional database access code required for the entity should be kept within its class. You can add new methods to perform other database operations (e.g. a findAllByOwner).

class Reservation extends ActiveRecord {
  /* ... */

  static findAllByOwner(ownerId) {
    Database.prepareQuery(`
      SELECT * FROM ${this.tableName} WHERE ownerId = :ownerId`,
      ownerId,
    ).execute();
  }
}
Enter fullscreen mode Exit fullscreen mode

In real practice, instead of plain Database calls you would use something like Builder pattern to dynamically create a query and execute it.

You may wonder how to use the approach I've brought up in the first part of the article - those plain domain objects with create factory function.

Because of ActiveRecord is an object-oriented design pattern, it is quite hard to adopt it in the context of functional programming. So it won't work straight away.

At the same time, it doesn't mean that other patterns initially authored in the context of OOP cannot be adopted in different contexts, especially with languages having both FP and OO elements (like JavaScript or Scala).

I'll cover the approach of building the persistence layer with those plain domain objects in one of the next articles.

I think, now we can narrow our discussion to ActiveRecord's strong and weak sides. This knowledge directly affects your ability to determine whether you encountered a suitable use case for the pattern.

Pros and Cons of ActiveRecord

Any pattern is a tool and each tool has its own strong and weak sides. If you are going to implement any pattern or, what happens constantly, use a library that implements one, I suggest you learning those sides. This way you will be more likely to pick a proper solution that fits your needs.

There are a few strong and weak spots of the pattern I've noticed in my practice. Let's start with strong ones.

  1. ActiveRecord is self-descriptive. Because of the close relationship between models and database tables, it’s easy to look into a project, examine its database schema, and you will have a decent level of understanding of the project.
    Some of the existent ORM libraries let you declare a list of columns (or properties) available in the corresponding table within the model class straight away. This way you'll have a complete definition of the models, relationships and business logic in one place (e.g. Typeorm, Laravel). Other provide external schema file (or at least migrations) where you can discover database tables structure (e.g. Rails).

  2. As this pattern gathers domain data, logic and database access together you can think about them as one conceptual piece that makes your code relatively simple. In most cases, you won't need to inject anything but a domain model to perform some business logic.

On the other hand, some disadvantages are deriving from both those points:

  1. On its own, the pattern encourages tight coupling between domain objects and your database. Every domain object knows about the database, tables, and schemas. This statement also implies that the pattern violates the single responsibility principle.

  2. Because the domain objects are mapped to the database schema, project refactoring becomes more difficult as changes in one, either domain or database design, will force you to adjust the other.

  3. It also increases the complexity of the logic in your domain objects as they will have to deal with both business logic concerns as well as database access (persistence concerns). A lot of relationships, inheritance, sophisticated entities and complex queries may make your code quite messy if you do not adapt patterns to tackle those problems.

ActiveRecord in the wild

There are many implementations of the pattern in different languages and frameworks of choice. A lot of popular frameworks built their ORM layer with ActiveRecord usage.

A small list of those with the link to the GitHub repo where you may check out their implementations. I recommend doing that if you're either a relatively experienced developer - your experience most likely precedes theoretical understanding, so it is useful for professional growth to see how other devs solve programming problems - or a developer specifically digs into project design and architecture and likes the subject.

There are also ActiveModel and ActiveResource in Rails but those are different concepts, not related to database or ORM.

  • Typeorm - great ORM written in JavaScript (TypeScript) that can work both on client and server sides (pretty much everywhere where you can run JavaScript). It is quite interesting how it adopts the idea of ES decorators (@decorateSomething) to declare an entity and to provide information about available properties.

ActiveRecord pattern in frontend

This section is speculation combined with my general professional experience. I have never used this approach in production so take my words with a grain of salt. The idea of applying development concepts and patterns in different contexts is interesting to me. I encourage you to share ideas and experiences on the subject if you encountered or tried this pattern in your frontend practice. You may share something that will be valuable to other devs and helps them. It will boost your karma πŸ˜‰.

One interesting moment is that this pattern is not applied frequently in the frontend, even though some frontend applications resemble UI for database CRUD operations.

One of the ActiveRecord examples I encountered in frontend development practice is BackboneJS, specifically, Model and Collection components. The model or a collection of models can be saved, destroyed, fetched and synced between client and server.

const Book = Backbone.Model.extend({
    defaults() {
      return {
        title: ''
        authorId: null,
        readStatus: ReadStatus.NOT_STARTED,
      };
    },

    startReading() {
      this.save({ readStatus: ReadStatus.READING });
    }
});

const Books = Backbone.Collection.extend({
  url: '/api/v1/books',
});

// Fetch list of user's books from endpoint /api/v1/books
Books.fetch();
Enter fullscreen mode Exit fullscreen mode

For quite a while Backbone is consider to be a legacy tool (probably for more than 5 years). But it is still worth learning how some problems were addressed with different patterns, even though the Backbone is no longer a tool-of-choice.

If you are a front-end engineer that cares about architecture, maintainability, and scalability of your solutions, it is worth in general exploring different frameworks like Backbone, React, Meteor, etc. They all adopted different patterns to solve common problems of building an effective web app architecture.

Another interesting proof of concept is ActiveResource.js library. More on the library you can read in introduction post on TopTal.com. I will provide a small summary (that pretty much can be found in the article) of the interface that the library empowers your domain entities with.

// Basic methods to retrive either a collection of or only one record
let users = await User.all();
let user = await User.first();
user = await User.last();

// Get a first record
user = await User.find('1');
// Find a record with the specified attribute
user = await User.findBy({ firstName: 'Jack' });

user.firstName = 'Jackie';

// Persist user changes to the server or handle the error if edit is not possible
try {
  await user.save();
} catch (userEditError) {
  // ...
}

/**
  * More complex example quering user records.
  * .includes() forces AR to fetch also user-related notifications 
  * (example of relationship).
  * .select() asks API to return only email and notifications 
  * (with message and createdAt) attributes.
  */
users = await User
  .includes('notifications')
  .select('email', { notifications: ['message', 'createdAt'] })
  .order({ email: 'desc' })
  .where({ admin: false })
  .perPage(10)
  .page(3)
  .all();
Enter fullscreen mode Exit fullscreen mode

Under the hood, these method calls are constructing a request to your API endpoint conforming JSON:API specification.

If you are not familiar with JSON:API, it is pretty much just a specification for REST APIs providing concrete rules for structuring JSON-based API responses. Check out project's documentation for more information and examples.

Quite interesting, that ActiveResource.js originates from Rails' ActiveResource class that provided similar functionality for backend models that are backed by other API providers (and not database). Backbone's Model and Collection have a similar background as they were inspired by Rails ActiveRecord ORM.

Nonetheless, as I said before, the ActiveRecord pattern is not frequently applied in the frontend world. One of the reasons, in my opinion, is that to implement ActiveRecord in client-side code your API (or APIs in more complex cases) should conform to some specification or convention and be consistent. Otherwise, you won't be able to reliably parse the responses on the client-side without writing a lot of boilerplate code for each entity endpoint.

Using ActiveRecord would also mean that you (as a frontend engineer) would build domain models on client-side (or at least some form of them) and use them, which raises a question on how to manage those models with conjunction to modern frontend stores like Redux or Ngrx. Especially when libraries encourage FP-like programming style which will force you to serialize them back and forth all the time (which doesn't seem to be sustainable) so the view layer would be notified about data changes.

Though it's still should be possible to use such domain models with observable-based store system, like MobX. As all model changes will be observed, you can pretty easily watch for those in view layer and, when needed, to perform actions to persist an updated model (or collection of models) through the model instance.

Summary

It seems to me, you've read a lot. Starting with the domain model concept being a part of any application (either implicitly or explicitly), we learned that it is represented by entities, their behaviors, and relationships.

The entities encapsulate application data that has to be persisted, and we need to manage the persistence code complexity to keep it reliable (fewer bugs, more safety) and maintainable.

To achieve that, we introduced ORM as an abstraction from plain database calls and queries and used the ActiveRecord pattern to provide database access for our domain entities so they could be persisted when needed. We also discussed the advantages and disadvantages of ActiveRecord, to have a slightly clearer picture about cases when it will be appropriate to use the pattern.

The last section was more like an incomplete study of how one may apply the ActiveRecord pattern in frontend development practice.


I hope you'll find the content of the article valuable on your path of becoming a pragmatic developer. I intend to continue creating such content and want to explore another part of a developer that can be called a "product creator". In my opinion, that identity should be improved and grown alongside with your engineer side.

I would appreciate your feedback after reading, whether you find the article content (architecture and design patterns) useful to you and how do you feel about long-read posts on similar subjects.

Top comments (0)