Implementing REST API using the layered architecture

Pragmatic Nerdz
Dec 13, 2020 - 5 Minutes

In this article, we have demonstrated a systematic approach for designing REST APIs, using as example a Blog platform. In this article, we are going to show how implement APIs using a layered architecture with the Blog platform as example. 

What is a layered Architecture?

Layered architecture is a way to organize systems in code units (or layers), each layer playing a specific role in the system. Although the layered architecture pattern does not specify the number and types of layers that must exist, most layered architectures consist of four standard layers: presentation, business, persistence, and database.

Example of Application Layers
Example of Application Layers
  1. Each layer has a specific role and responsibility within the architecture. For example, the presentation layer is responsible for handling all user interface and browser communication, whereas a business layer is responsible for executing specific business rules associated with the request.
  2. Layers are isolated from each other, having no knowledge of the inner workings of other layers.
  3. Each layer communicate only with the layer beneath it. This mean the presentation layer communicate only with business layer, and should not communicate directly with persistence layer.
  4. Data is flowing from one layer to another.
    Request from client are received by the Presentation layer, transformed to Domain Objects are the are moving to lower layers.
    Response are transformed from Domain Objects coming from lower layers.

Implementing Domain Objects

During the design phase,  the Resources of the API Object Model that have been identified should be implemented as Domain Objects. Each Domain Object will contains the state of its associated Resource.
For the Blog API, we have identified as Resources Article and Comment. We will create Domain Objects for each of those resources, and it will look like:

class Article {
  val id: Long,
  val title: String,
  val author: String,
  val email: String,
  val publishedDate: Date,
  val status: String,
  val summary: String,
  val language: String
  val content: String
}

class Comment {
  val id: Long,
  val article: Article,
  val text: String,
  val author: String,
  val email: String,
  val commentDate: Date,
  val language: String,
  val sentiment: Float
}

Layering REST API

Blog API Layers, with Article components
Blog API Layers, with Article components

Database Layer

The database layer responsibility is to store the Domain Objects. This responsibility is the same, regardless if your are using a Relational database like MySQL, a JSON database like Google Firebase Database or Cloud key/value pair database like DynamoDB.

In the Blog API, we will use MySQL, and each Domain Object is stored in a table:

  • The table  T_ARTICLE for storing Article.
  • The table T_COMMENT for storing Comment.

The script for creating the tables in the Database layer will look like this:

CREATE TABLE T_ARTICLE(
  id BIGINT AUTO INCREMENT,
  title VARCHAR(255),
  summary VARCHAR(255),
  content TEXT,
  language VARCHAR(2),
  author VARCHAR(100),
  email VARCHAR(100),
  status VARCHAR(100),
  published_date_time DATETIME,
  
  PRIMARY KEY (id)
);

CREATE TABLE T_COMMENT(
  id BIGINT AUTO INCREMENT,
  article_fk BIGINT, 
  author VARCHAR (100),
  email VARCHAR(100),
  text TEXT,
  language VARCHAR(2),
  comment_date_time DATETIME,

  FOREIGN KEY (article_fk) REFERENCES T_ARTICLE(id),
  PRIMARY KEY (id)
);

Persistence Layer

This persistence layer responsibility is to provide an interface for accessing the database. This layer contains Repositories or Data Access Object,  which implement the Create, Retrieve, Update and Delete (CRUD) operations.

Receive my Stories your e-mail inbox as soon as I publish them.
Subscribe to my Blog

It's common practice to use Object Relational Mapping (ORM) frameworks like Hibernate, Doctrine etc. to implement the CRUD operations without writing cumbersome SQLs statements.

In the Blog API, Article and Comment persistence will be handled respectively by ArticleRespository and CommentRepository, and their code will look like:

interface ArticleRepository {
  fun findAll(): List<Article>
  fun findById(id: Long): Article
  fun save(article: Article): Article
  fun delete(article: Article)
}

interface CommentRepository {
  fun findByArticle(article: Article): List<Comment>
  fun findById(id: Long): Comment
  fun save(comment: Comment): Comment
}

Business Layer

The business layer responsibility is to implement  the core logic of your business. Service are the classes used in this layer to implement  the business logic.

In the Blog API, Article and Comment business logic will be implemented respectively by ArticleService and CommentService. Here is an example of implementation of ArticleService:

class ArticleService {
  val articleRepository: ArticleRepository

  fun create(request: CreateArticleRequest): Article {
     val article = createArticle(request)
     return articleRepository.save(article)
  }

  private fun createArticle(request: CreateArticleRequest): Article {
    ...
  }


  fun get(id: Long): Article {
     return articleRepository.findById(id)
  }


  fun delete(id: Long) {
     articleReponsitory.delete(get(id))
  }
}

Note that ArticleService interacts only with ArticleRepository. This shows  that the Business layer communicates only with Repository layer.

Presentation Layer

The Presentation layer responsibility is to handle clients requests sent over HTTP. This layer contains Controllers, that receive client requests, forward them to the Service layer and return results to clients.

The data exchanged between the Controller and the client are done in JSON (or XML) format, over HTTP. All modern programing language offer capabilities to automatically convert back and forth JSON (or XML) to Objects.

In the Blog API, Article and Comment controllers are implemented respectively in ArticleController and CommentController. Here is an example of implementation of ArticleController:

class ArticleController {
  val articleService: ArticleService 

  // POST /articles
  fun create(request: CreateArticleRequest): CreateArticleResponse {
     val article = articleService.create(request)
     return toCreateArticleResponse(article)
  }
  private fun toCreateArticleResponse(article: Article): CreateArticleResponse {
     return CreateArticleResponse(article.id)
  }

  // GET /articles/{id}
  fun get(id: Long): GetArticleResponse {
     val article = articleService.get(id)
     return toArticleResponse(article)
  }

  private to GetArticleResponse(article: GetArticleResponse) {    
    return GetArticleResponse(
      ....
    );
  }

  // DELETE /articles/{id}
  fun delete(id: Long) {
     articleService.delete(id)
  }
...
}

Note that ArticleController interacts only with ArticleService. This shows that the Representation layer communicates only with Service layer.

Advantages to Layered architecture

Layered architecture enforces strong Separation of Concern, which provide the following advantages:

  1. Consistency: It provide a consistent pattern for organizing your application. So it's very easy any developer who is familiar with this architectural pattern to navigate through the code.
  2. Simplicity: Because each layer address only one concern, we end up with classes having functions with very few line of code, which simplify and improve the readability of the code.
  3. Maintainability: It's much simpler to modify individual layer of the system. The fact that layer are independant, change can be done with minimal impact to other layers.

Next Stories in the Serie

REST Api
Api
Design Pattern
Layered Architecture
Separation Of Concern