Skip to main content

Services

Introduction


In the context of software architecture [...], the term service refers to a software functionality or a set of software functionalities with a purpose that different clients can reuse for different purposes, together with the policies that should control its usage.

Definition


On our application, we understand a service as a Javascript Class that serves a specific purpose associated to a particular part of the domain model.

Let's break this down into pieces:

  • Javascript Class: a plain javascript class, that encapsulates logic and allows reusability across clients or consumers

  • specific purpose: it has a defined scope, which is its responsibility

  • part of the domain model: the purpose of the service is related to the domain model. If we compose all the services in the application, we will be able to build the whole model.

We can separate services in two different types:

API Services


As the name states, these are meant to abstract the communication with an API. It could be responsible for handling the requests to the whole API or just a specific part of it.

Usually, each module will have its own API Service that will communicate with a specific part of our own API.

Every single request that is fired from our application will be placed within a service.

They will be placed under your module/services folder in a file called {my-service-name}.service.ts.

We will use the library Axios to make API calls.

Example

As many services can share the same requests config, as header or interceptors, we will create a specific Axios instance by API and inject the required instance into the service constructor.

// core/axios.ts

import axios from 'axios';

export const pokemonApiAxiosInstance = axios.create({
headers: {
accept: 'application/json',
'content-type': 'application/json'
}
});
// pokemon-randomizer/services/pokedex.service.ts

import { AxiosInstance } from 'axios';
import { pokemonApiAxiosInstance } from '../../core/axios';
import { Pokemon } from '../types/pokemon-randomizer.types';

class PokedexService {
axiosInstance: AxiosInstance;

constructor(axiosInstance: AxiosInstance) {
this.axiosInstance = axiosInstance;
}

getPokemonAPIRequest(id: number): Promise<Pokemon> {
return this.axiosInstance
.get(`https://pokeapi.co/api/v2/pokemon/${id}`)
.then((response: { data: Pokemon }) => response.data);
}
}

export const pokedexService = new PokedexService(pokemonApiAxiosInstance);

A couple of things to highlight:

  • we are not exporting the class but rather an instance of it, this is meant to build a Singleton Pattern. This way we allow the possibility of sharing the service's state during the execution time.

  • we are appending APIRequest to each function that fires a request, to easily identify this operation from the outside

  • as the state of the service will persist during the execution time, it's a good place to apply different kinds of logic to store parsed data, cache information or handle complex requests logic to improve readability of the code through encapsulation and gain performance of the application.

Utility Services


As the name states, these are meant to provide a utility service to the application. They will have a defined responsibility and encapsulate the logic to provide their defined purpose.

They will be placed under your src/services/my-service-name with the name {my-service-name}.service.ts.

As they provide utilities, they are not strictly related to the application, so they are not considered part of the modules.

Think of them as third-party npm packages that we need to install to use on our app, but this time they're ours, we are the ones building them. Aim to build them properly, so we can publish them to the npm registry and reuse them on different places. Be smart.

Example of these services could be: Web Socket communication, Internationalization, Validators, Error Handling, Date Handling, etc.