DEV Community

Oghenovo Usiwoma
Oghenovo Usiwoma

Posted on

A good reason to create a Service Layer in your application

Many developers are accustomed to the design pattern in which we break our application code into the models/entities layer, the repository layer, the service layer, and the controllers.

The reason for the entities layer is pretty straightforward: you must declare the fields(and their types) of entities in your application. In some applications, you can also describe the database schema in your entity class. You place your queries in the repository layer and use them to retrieve data, while your controllers contain handlers for each route and HTTP method. But what about Services?

I occasionally have service classes that appear to have no use other than to satisfy the need to have a service layer before my repositories.

I recently discovered a great reason to have service layer classes. A project I was working on had no clearly defined service layer. I needed to write code to pull data from different repos, process it and return the result. For the sake of this article, let's assume that I first wrote this code in the controller class.

const repo1 = new Repo1()
const repo2 = new Repo2()

@rest('/demo')
class DemoController {

  @get('/result')
  getResult(req) {
    // handler for GET /demo/result

    const stuff = repo1.findById(req.body.id)
    const moreStuff = repo2.findAllCreatedBefore(req.body.time)
    // do some processing here
    return { result } 
  }

}
Enter fullscreen mode Exit fullscreen mode

Then I realized I needed it somewhere else, so I transferred it to a different file and wrapped the code in a function.

// process-data file
const repo1 = new Repo1()
const repo2 = new Repo2()

export function processData(id, time) {
  const stuff = repo1.findById(id)
  const moreStuff = repo2.findAllCreatedBefore(time)
  // do some processing here
  return result;
}
Enter fullscreen mode Exit fullscreen mode

Now that I can use it everywhere, isn't my job done? No, not exactly. There is only one problem: How will I test this function?

The function has dependencies (the repositories) that are not part of its parameters which will make testing difficult. This function needs to use a set of mocked dependencies in my test cases. How do I go about doing this? Wouldn't a class be appropriate here?

// process-data file

export class DataProcessor {
  private repo1
  private repo2

  constructor(repo1, repo2) {
    this.repo1 = repo1
    this.repo2 = repo2
  }

  processDataByIdCreatedBeforeTime(id, time) {
    const stuff = this.repo1.findById(id)
    const moreStuff = this.repo2.findAllCreatedBefore(time)
    // do some processing here
    return result;
  }

}

export const processor = new DataProcessor(new Repo1(), new Repo2())

Enter fullscreen mode Exit fullscreen mode
// test DataProcessor

it('should process the data correctly', () => {
  const processor = new DataProcessor(mock1, mock2)
  // ...some testing code
  const result = processor.processDataByIdCreatedBeforeTime(id, time)
  assert.equal(result, expectedResult)
})

Enter fullscreen mode Exit fullscreen mode

In the example above, doesn't the DataProcessor class look like a Service layer class? The need to test the data processing logic eventually led to the creation of the service layer class. So, what should we take away?

  • You need the Service layer separate from your repositories and controllers so you can unit-test your business logic code
  • You also need the Service layer so that your business logic code can be reused in other parts of your application
  • Consider using Test Driven Development?

This is my opinion. I suspect that there may be contrary ideas or I may have missed something, in that case, feel free to share in the comments.
Thank you for reading.

Top comments (2)

Collapse
 
nanumichael27 profile image
Nanu Oghenetega

Wonderful one Novo

Collapse
 
edwardoboh profile image
Edward Oboh

And I never thought of it from this angle. Really nice one.