Guidelines Software Architecture - global-121/121-platform GitHub Wiki

This page contains the guidelines we have for backend development.The intended audience is anyone who wants to develop for the 121-platform. The current code base does not follow all these guidelines, but the intent is that any new code or refactoring follows these guidelines.

Guidelines Software Architecture

Encapsulation guidelines

  • We keep datamodel loosely coupled (3e normalform)
    • We don't want to move to microservices for now
  • Each entity can only be created/updated/deleted within 1 module
    • Reading entities from other modules is allowed
    • But keep this limited with clear reasoning per entity (evaluate current list of imported entities)
  • Put static function that are used in multiple places in the code in separate 'utils' module:
  • When to import services from other modules:
    • Because that other service modifies entities of that module (like transaction service being imported in payment service for storing transactions)
    • Because the other service make external api calls
    • Because the other service is static utils service
    • Because the other service implements a complex read query to avoid code duplication / this could later be changed to repository inheritance
    • authentication
  • When to import entities from other modules:
    • do not import entities from another module if you are gonna use it to create/update/delete the entity
    • no general guidelines, but should be used sparsely. check case-by-case if it's really needed

API-design guidelines:

  • Organize API around entities and not use cases
  • Apply proper HTTP methods:
    • GET/POST/DELETE/PUT/PATCH
    • Don't use POST for GET to be able to pass payload/body
  • Apply proper status codes in response:
    • Document all specifically relevant status codes per endpoint
  • Naming:
    • Don't use verbs, only nouns; there can be exceptions, for example for tasks that go beyond simple manipulation of a resource, like retrying or approving a payment. In that case use /retry or /approve at the end.
    • Use plural/singular per best practice
    • Use IDs in names as per this table
    • Limit to 2 levels in naming ("/noun/id/noun/id") > figure out how this works in practice
  • Limit response to 2 levels deep (so relation of a relation is still OK)
  • Use same endpoint with query-parameter for response format options (e.g. export vs json for PA-table)
  • Improve documentation in separate PBI: #18132

Naming guidelines

  • Name things with their full name, do not use abbreviations. Let your IDE auto-complete names, so no RSI because of long names.
  • Class names are plural for Modules, Controllers and Services. For example: ProgramsModule, ProgramsService, ProgramsController. Reflected in the file names as well. For example: programs.module.ts, programs.service.ts, programs.controller.ts.
  • Class names are singular for Entities. For example: ProgramEntity. With file name: program.entity.ts.
  • Base folder names of a Module are plural. For example: src/programs/

121 Service Guidelines

  1. Every NestJS Module has a single and clearly defined responsibility.

  2. Every Service has a single and clearly defined responsibility.

  3. All database interactions are in Repositories.

  4. A NestJS Module only uses Repositories that belong to their own and Modules lower in the NestJS Module Hierarchy.

  5. Functions only accept and return DTOs and not Entities.

121 Portal Guidelines

File / Folder structure

This is our desired file & folder structure:

app
├── components
│   └── component-name
│       ├── component-name.component.html
│       ├── component-name.component.scss
│       ├── component-name.component.spec.ts
│       └── component-name.component.ts
├── directives
│   ├── directive-name.directive.spec.ts
│   └── directive-name.directive.ts
├── enums
│   └── enum-name.enum.ts
├── mocks
│   ├── mock-name.mock.ts
│   └── helpers.ts
├── models
│   ├── model-name.api.model.ts
│   └── model-name.model.ts
├── pages/
│   └── page-name/
│       ├── components/
|       |   └── page-specific-component/
|       |       ├── ...
│       ├── page-name.module.ts
│       ├── page-name.page.html
│       ├── page-name.page.scss
│       └── page-name.page.ts
└── services
    ├── service-name.service.spec.ts
    └── service-name.service.ts
  • No new top-level (ie. direct descendants of app) folders should be added.
  • Domain-specific folders should be inside app/pages
  • The models folder should only contain the representation of entities that come from the backend
  • A domain-specific folder (such as app/pages/payment) or a top-level component folder (such as app/components/data-table) can replicate app's top-level folder structure. For example, we could have a components and directives folder within app/pages/payments or an enum folder/file within app/components/data-table.

When creating a new component/enum/directive, where do I put it?

By default, create it close to where it would be used. (ie. create a component that is specific to payments within the payments domain).

Only move it to the equivalent top-level folder when

  • It will inevitably be used by more than one domain (eg. a design atom like Button)
  • It becomes used by more than one domain