Backend
How to

General
 concepts
used

Dependency injection, or DI

Dependencies are services or objects that a class needs to perform its function. Dependency injection, or DI, is a design pattern in which a class requests dependencies from external sources rather than creating them.

https://angular.io/guide/dependency-injection
export class AuthorizationUseCases {
  private readonly sessionSource: SessionSource,
  private readonly fieldSource: FieldSource,
  private readonly loggerSource: LoggerSource

  constructor() {
    this.sessionSource = new SessionRepository()
    this.fieldSource = new FieldRepository()
    this.loggerSource = new LoggerProvider()
  }

Knows about dependencies construction

Make a new instance of each dependencies
on AuthorizationUseCases creation 

constructor injection

dependencies

const authorizationUseCase = new AuthorizationUseCase(
  new SessionRepository(), 
  new FieldRepository(), 
  new LoggerProvider()
)

do not know about dependencies construction

Take the provided instance without of taking care of how they have been created 

Dependency Inversion, or DI

 

  1. High-level modules should not import anything from low-level modules. Both should depend on abstractions (e.g., interfaces).
  2. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
https://en.wikipedia.org/wiki/Dependency_inversion_principle
export class ForecastsUseCases {

  constructor(
    private readonly meteoblueClient: MeteoblueClient
  ) {
  }
  
  public getHourlyForecasts(location: {lat: number, lon: number}): Promise<ExpectedResult> {
    /*
     * A lot of business logic here
     * 
     */
    
    const result = await this.forecasts.get(`/multimodel-1h`, {
      params: {
        lat,
        lon,
      },
    }); 
    const forecasts =  this.toForecasts(result.data); 
    
    /*
     * 
     * 
     * A lot of business logic here
     * 
     */
    
  	return expectedResult
}

specific to meteoblue...

export class ForecastsUseCases {

  constructor(
    private readonly forecastsSource: ForecastsSource
  ) {}
  
  public getHourlyForecasts(location: {lat: number, lon: number}): Promise<ExpectedResult> {
    // A lot of business logic here
    const forecasts = forecastsSource.get(location)
    return expectedResult
}
  
  
export class forecastsFromMeteoBlue implements ForecastsSource {

  constructor(
    private readonly meteoblueClient: MeteoblueClient
  ) {}
  
  public getHourly(location: {lat: number, lon: number}): Promise<HourlyForecasts> {
    //A lot of business logic here
    const result = await this.meteoblueClient.get(`/multimodel-1h`, {
      params: { lat, lon },
    }); 
    return this.toForecasts(result.data); 
}
interface ForecastsSource {
  getHourlyForecasts(location: {lat: number, lon: number}): Promise<HourlyForecasts>
}

Easy to change

Switchable

ex 1 : Switching deps

UserMysql

UserAPI

UseCase

Source

New source without changing the use case

ex 2 : Easy to unit test

sql adapter

In memory

UseCase

Source

Unit Test

  • Test by use case
  • No mock in test
  • Ability to test effect (ex: insertion, read...)
  • No Implementation details coupling
  • ....

Nest framework concept used

Needed dependencies

Say here how to build your dependency

Give your dependency to nest DI (dependency injection)

How to make a dependency injection ?


async function boostrap() {
  NestFactory.create(AppModule)
}
main.ts
app.module.ts / Root module
rest-api.module.ts
authorization.module.ts

@nestjs/common

Module : create scope/isolation and manage dependencies (reuse etc...)

Decorators : Controller / Guard / Route

@nestjs/common

How nest could help me to connect with the outside world ?

How do i apply same logic accross all requests ?

Guard : CanActivate implementation 

Implementation example

Demo Ready

Delay technical choice
and having a working software quickly
by using an in memory source

API
(not ready)

wich DB ?

standalone app

🤔

🤗 🚀

Where do i put my egg in nest ?

Where do i implement my feature ?

1 - first in "app" : to implement your sencrop app related use-cases

2 - then in "infrastructure" : to connect with the external world

How to develop my first feature ?

How to start my first implementation ?

1 - Specify your use-case :

authorization.use-cases.spec.ts
authorization.use-cases.ts
session.in-memory.ts

Make a PR 🚀

2 - Connect it a real world source

export interface SessionSource {
  findOneByUserId(userId: number): Promise<Session | undefined>;
}
authorization.use-cases.ts
session.source.ts
session.repository.ts

Make a PR 🚀

3 - Make it available for an external usage

a - Export

b - Import

c - Use

authorization.module.ts
rest-api.module.ts
admin.guard.ts

Make a PR 🚀

Benefits

Readable Structure

E2E Ready

Fast E2E on CI => available on every commit

First increment to E2E test 

Using E2E locally and on CI without DB !

Second increment to E2E test 

Using E2E for release by just switch deps

Modulare software

Domain A

Domain B

Domain C

  • Reuse of services (!= use cases)
  • Independant or sharing dependency (with nest)
  • Runtime "private package" emulated

Pitfalls

Abstraction

Hopefully we had a refactorable code !
#thxCleanArchi
#thxTestByUseCase

3 months to remove the bad abstraction...

Abstraction

duplication is far cheaper than the wrong abstraction

prefer duplication over the wrong abstraction

What we learn ?

Our story in video : 

Use-case usage

By reusing use-cases we had:
- understanding issues
- Testing issues
- Perfomance issues

  • A UseCase should the most independant it can be
  • A UseCase is not a service, it's the entry point to the domain layer and it might orchestrate several service... 
  • Avoid useCase reuse if you can

NestJs, DbLib, ApiLib

Avoid NestJs or over tools (lib) interference with you business because it will increase the complexity

One update can make you change a lot of code, be careful
(so avoid it)

Use some trick like Dependency Inversion to do not depend on them...

Source/Port

By Making too specific "queries" you might put business on it  

Ex: 

SELECT u.name as universe_name,
MAX(case when (c.scheduled_day >= $date) then NULL ELSE c.scheduled_day end) as last_shoot_date,
MIN(case when (c.scheduled_day <= $date) then NULL ELSE c.scheduled_day end) as next_shoot_date
FROM ad_universe u
LEFT JOIN ad_campaign_universe cu ON u.id = cu.universe_id
LEFT JOIN ad_campaign c ON c.id = cu.campaign_id OR c.domain = u.domain AND c.all_universes
WHERE u.domain = $domain
AND c.enabled
AND u.enabled
GROUP BY universe_name

By Making too CRUD "queries" you might:

  • - Introduce coupling to attribute you do not need
  • - have perfomance issue (ex: blocking constraint with transaction)  

Ex: update(user) but the use-case just want to update one field here...

Transaction

We had perfomance issues by using:

  • Business transaction
  • Too strict transaction mode

So... Think twice before adding a transaction...

Think twice before adding a transaction !!!

Keep the transaction in the adapter side or be careful

Dependencies

We have some cyclique dependencies issues between :

- Files (import)

- Classes

This kind of pb is hard to debug !

We have avoided it by :
- cyclic import detection

"circular-deps:find": "npx madge --circular --extensions ts ./src/"

- using only required dependency (ies?)
- Avoiding import between domain context
- Avoiding unused export :

"ts-prune": "npx ts-prune",

In memory

no business responsibility

serviceA()

doExternalCall()

function serviceA(n) {
	doExternalCall()
}
function doExternalCall(n) {
	// complex call orchestration
	// complex sql Request
	...
}

behaviour tree

}

PB business responsibility delegated to the integration part

Integration part

business part ?

}

PB complex integration test needed

Moving business responsability

serviceA()

doExternalCall()

function serviceA(n) {
	// business logic
	doExternalCall()
}
function doExternalCall() {
	// simple sql or api request
}

behaviour tree

}

simple integration test

Integration part

business part

}

No Business responsibility
delegated

Moving business responsability

serviceA()

doExternalCall()

function serviceA(n) {
	// business logic
	doCall()
}
function doExternalCall() {
	// simple sql or api request
	// here external call
}

behaviour tree

simple unit test

Integration part

business part

}

In memory state

function doInMemoryCall() {
	// simple logic
}

How we do it ?

1 - Looking at article, video(s), book(s)

2 - First draft with standalone mode

Clean Architecture Book

3 - Structure choice (how to reuse)

4 - Testing with inMemory

5 - Modularization
... - ....

How to manage errors ?

1 - Manage business error in the use-case not in the adapter
Ex : findUser(id: string): Promise<User | undefined>
not : findUser(id: string): Promise<User>

 

2 - Always give the initial root cause (use cause params)

 

3 - Always throw error after a try catch or log an error

 

4 - Always bind it to a HTTP Error (if not -> 500)

 

5 - Observe your error (trace, logs...)

Errors best practices

Business Errors

Some naming and definitions

my-use-case.use-case.ts

my-source.source.ts

my-controller.controller.ts

my-module.module.ts

my-guard.guard.ts

my-service.service.ts

my-client.client.ts

my-mapper.mapper.ts

my-model.model.ts

my-source.source.shared.ts

my-unit-test.test.ts

my-unit-test.spec.ts

my-integration-test.itest.ts

my-type.type.ts

my-adapter.[adapterName].ts

my-db-adapter.repository.ts

my-adapter.[adapterName].ts

my-local-adapter.local.ts

my-in-memory-adapter.in-memory.ts

my-type.decorator.ts

Made with Slides.com