Skip to main content

Command Palette

Search for a command to run...

Implementing the Persistence Layer - Part 2: Repositories

Clean Architecture Series - Frameworks Layer

Updated
3 min read
Implementing the Persistence Layer - Part 2: Repositories
M

I'm an enthusiastic Chilean software engineer in New Zeland. I mostly focus on the back-end of the systems. This is my site, Señor Developer, where I share my knowledge and experience.

The repository pattern provide a layer of abstraction between the application code and the data storage. Instead of directly interacting with the database, your application interacts with the repository, which handles all the database operations.

Policy Repository

At this point we need to use the "IPolicyRepository" interface located in the Domain layer which defines the PolicyRepository behavior.

Go to frameworks folder and create a new folder named "repositories" with a subfolder named "policy" into the "persistence" folder. And create a file named "policy.repository.ts" into the "repositories" folder:

// .src/frameworks/persistence/repositories/policy/policy.repository.ts
import { IPolicyRepository } from "@/domain/policy/policy-repository.interface";
import { PolicyEntity } from "@/domain/policy/policy.entity";
import { IDatabase } from "../../db/database.interface";
import { PolicyMapper } from "../../mappers/policy.mapper";

export class PolicyRepository implements IPolicyRepository {
  constructor(private db: IDatabase) {}

  async save(policy: PolicyEntity): Promise<void> {
    // Mapping the PolicyEntity to PolicyModel
    const policyModel = PolicyMapper.toPersistence(policy);
    // Get columns name from the PolicyModel
    const columns = Object.keys(policyModel);
    // Get columns values from the PolicyModel
    const values = Object.values(policyModel);
    // Placeholder to set the column values
    const placeholder = Array(columns.length).fill("?");
    // Preparing the SQL statement
    const sql = `INSERT INTO policy (${columns.join(",")}) VALUES (${placeholder.join(",")})`;
    // Execute the statement into DB
    await this.db.query(sql, values);
  }
}

Let's explain the PolicyRepository Class:

  • constructor: Rather than directly creating an instance of the Database, we define an interface IDatabase for the database connection and use Constructor Dependency Injection to manage it.

  • save: Encapsulate the logic to create the SQL statement with the PolicyEntity information and execute the query into the database. Also, this method map the PolicyEntity to PolicyModel to get the db object representation before persist it.

\By implementing IPolicyRepository interface we must implement all methods. In our case, we only have the "save" method.*

The Policy Mapper

The PolicyMapper is used to convert data between database and domain layer. It helps us in decoupling the source and target objects by providing a separate mapper method that handles the conversion logic.

Go to frameworks folder and create a new folder named "mappers" into the "persistence" folder. And create a file named "policy.mapper.ts":

// ./src/frameworks/persistence/mappers/policy.mapper.ts

import { PolicyEntity } from "@/domain/policy/policy.entity";
import { PolicyModel } from "../db/sqlite/policy.model";

export class PolicyMapper {
  static toDomain(data: PolicyModel): PolicyEntity {
    const { start_date, price: _, ...rest } = data;
    return new PolicyEntity({
      ...rest,
      startDate: start_date,
    });
  }

  static toPersistence(policy: PolicyEntity): PolicyModel {
    const { startDate: start_date, ...rest } = policy.unmarshalled();
    return {
      ...rest,
      start_date,
    };
  }
}

toDomain: This method converts PolicyModel object to a PolicyEntity object by applying the conversion logic for startDate (taking the start date information from the start_date column)

toPersistence: This method converts PolicyEntity object to a PolicyModel object by applying the conversion logic for start_date (taking the start date information from the startDate property)

In conclusion, Using a mapper we hide the logic to map between DB and Domain make the conversion logic reusable and testable.

See you in the next article.

Clean Architecture with Typescript

Part 3 of 10

In this series, I will show you the clean architecture system and how to implement it in nodejs and typescript.

Up next

Implementing the Persistence Layer - Part 1: Database

Clean Architecture Series - Frameworks Layer