Skip to main content

sui-domain

Backbone for creating a domain that complains with the guidelines of Schibsted Spain

Motivation​

sui-domain provides:

  • Avoid repeating boilerplate code by extracting some on this library.
  • Enforce all to follow same rules while creating a new domain.
  • A HttpFetcher to make http requests
  • Set of domain objects to extend
  • A utility to create an entry point

Installation​

$ npm install @s-ui/domain --save

Using EntryPoint​

import {EntryPointFactory} from '@s-ui/domain'

// useCases is an object with a key with the name of the use case
// and an array with a function to import the factory and the
// useCase name as string
const useCases = {
current_user: [() => import('./user/UseCases/factory'), 'currentUserUseCase'],
products_search: [
() => import('./search/UseCases/factory'),
'productsSearchUseCase'
],
real_estate_detail: [
() => import('./detail/UseCases/factory'),
'realEstateDetailUseCase'
]
}

// config could be a simple object or a more complicated
// creation like a class Config with methods to get and set
const config = {
DETAIL_ENDPOINT: 'http://api.url.com/detail'
}

// that returns you an instantiable EntryPoint class
const EntryPoint = EntryPointFactory({config, useCases})
const domain = new EntryPoint()

// if you don't want to share the config between instances
// you could pass the config directly to the constructor
// useful if you're mutating the config for storing values
const EntryPoint = EntryPointFactory({useCases})
const domain = new EntryPoint({config})

Injecting a logger to our domain​

In order to increase the observability of your domain, you can pass a custom logger to your domain instance and it will be injected on each UseCase factory. Following the example of previous section:

How to inject the logger to your domain?​

// Logger mock example
const logger = {
log: () => console.log('⚠️')
}

const EntryPoint = EntryPointFactory({config, useCases, logger})
const domain = new EntryPoint()

How use it on your domain?​

// Your factory file
import EmptyUseCase from './EmptyUseCase.js'

export default ({config, logger}) => new EmptyUseCase({config, logger})

// Your UseCase implementation
export default class UseCase {
constructor({config, logger}) {
this._config = config
this._logger = logger
}

execute() {
this._logger.log() // Logger ready to rock! 🎸
return Promise.resolve(true)
}
}

Using Fetcher​

import {FetcherFactory} from '@s-ui/domain'
import UserEntitiesFactory from '../../user/Entities/factory'
import UserValueObjectsFactory from '../../user/ValueObjects/factory'

import HTTPUserRepository from './HTTPUserRepository'

export default class UserRepositoriesFactory {
static hTTPUserRepository = ({config}) =>
new HTTPUserRepository({
config,
fetcher: FetcherFactory.httpFetcher(),
userEntityFactory: UserEntitiesFactory.userEntity,
emptyUserValueObjectFactory: UserValueObjectsFactory.emptyUserValueObject
})
}

Fetcher exceptions interception​

Aditionally, it's possible to require a special version of the http fetcher with which is possible to intercept all errors in one single point of the application.

This feature allows to handle generic http errors in a central and unique function of the web application.

For example, this could be useful if it's needed to perform a specific action every time a 401 status code is retrieved as a result of an http request. (i.e. to redirect the user back to the login page)

How to require the interceptable fetcher​

To be able to use this feature, instead of initializing the fetcher normally, it is needed to invoke the following method of the FetcherFactory class:

const fetcher = FetcherFactory.interceptableHttpFetcher()

The interceptableHttpFetcher is fully retrocompatible with the standard httpFetcher class, so there is no need to adapt any code before doing this change.

Setting a function to intercept errors​

Once the interceptableHttpFetcher has been required and is being used to perform http requests, it's possible to set a function that will be invoked every time an error occurs when performing an http request.

This is the way the callback function can be defined:

fetcher.setErrorInterceptor({
callback: error => {
if (result.isAxiosError === true) {
const statusCode = result.response.status
// Do something...
}
}
})

Using a domain object​

import {UseCase} from '@s-ui/domain'

export default class CurrentUserUseCase extends UseCase {
constructor({service} = {}) {
super()
this._service = service
}

async execute() {
const userEntity = await this._service.execute()
return userEntity.toJSON()
}
}
import {Service} from '@s-ui/domain'

export default class CurrentUserService extends Service {
constructor({repository} = {}) {
super()
this._repository = repository
}

async execute() {
const userEntity = this._repository.current()
return userEntity
}
}

Listen a useCase​

@s-ui/domain includes a way to subscribe to every useCase execution without the need of using any kind of decorator or external dependency.

This is useful if you have side effects in a different place from where you're executing the useCase of the domain.

domain
.get('generate_search_url_search_use_case')
.subscribe(({params, error, result}) => {
// doSomething when the useCase generate_search_url_search_use_case is called in other place
})

If you want unsubscribe any useCase execution

const subscribedUseCase$ = domain
.get('generate_search_url_search_use_case')
.subscribe(({params, error, result}) => {
// doSomething when the useCase generate_search_url_search_use_case is called in other place
})

subscribedUseCase$.unsubscribe()