Skip to content

Instantly share code, notes, and snippets.

@savishy
Last active January 22, 2024 07:30
Show Gist options
  • Save savishy/9b7839a346b179f08e4cc8e6fa6ef1b4 to your computer and use it in GitHub Desktop.
Save savishy/9b7839a346b179f08e4cc8e6fa6ef1b4 to your computer and use it in GitHub Desktop.
Notes from "Patterns of Enterprise Application Architecture" book and "Microservices" Learning Path on Pluralsight

Layers

(From the Pluralsight course)

  • Presentation Layer
    • showing data to and accepting input from end users
  • Service Layer
    • BFF (Backend for Frontend) or API Gateway.
    • Single point of entry for all outside traffic.
    • Present stable API to Presentation Layer, and allow μs to change independent of frontend.
  • Microservices
    • Implement Domain Logic here.
    • Implement Data Access here.
    • Separate domain logic and data access.

Which μs should hold a particular domain logic?

(From the Pluralsight course)

💡 Apply the domain logic and data-access patterns which make most sense to each individual microservice.

Example of eShopOnContainers e-commerce application that has Catalog, Order and Basket μs. Now we want to roll out some promotions ("add two T-Shirts to basket, get a cup free").

  1. Where should the "promotions" data be contained?
  2. Where should the "promotions" logic be contained?

One option:

  • Store promotions data in the Catalog μs.
  • Store promotions logic in the Basket or Order μs.

image

If the promotions become more complex, consider rolling out a new μs:

image

❗ What to avoid

  • Avoid building domain logic into the API Gateway (BFF) or the Frontend Applications.
  • avoid duplicating domain logic across μs
  • do not give too many responsibilities to one μs
  • avoid chatty communications across μs in the context of one user operation.
    • prefer async communication between μs.

Domain Logic Patterns

Transaction Script

(From the Pluralsight course)

  • A single procedure for each request from presentation layer
  • Mixes Domain Logic and Data Access in same function (perhaps with a thin wrapper around Database)

Pros ✅

  • Simple as long as domain logic is simple
  • easy to see big picture, reason about, easy to adopt

Cons ❌

  • as domain logic increases, methods get complex
  • because Data Access and Domain Logic are mixed together, methods cannot be effectively unit-tested
  • Duplication is possible if different frontend applications need to reuse business logic

Domain Model Pattern

  • Exposes public methods on our objects that represent business operations.
  • Uses the language of the business.
  • Does not contain any data access code.
  • E.g. an Order can only go through specific statuses and it can only transition between statuses in specific sequence.

Examples of carefully controlling access to the state of the order image

image

The only way these models can be modified is by intentionally exposing only one public method that adds items to order.

image

Data Mapping Layer

💡 Domain Model pattern does not have any references to data access in objects. Data access is done through the Data Mapping Layer.

  • Retrieve data from database
  • Convert it into objects used by the domain logic.
  • These objects could be
  • Domain Models OR
  • Data Transfer Objects

Pros

  • Model never goes into an invalid state or goes through an invalid transition between states

Approaches to Data Mapping

Object Relational Mapper (ORM) features:

  • Auto-generate SQL statements
  • Navigate relationships
  • Track modifications
  • Simplify data access.

Example, if a Orders table ha a 1:many relationship to an OrderItems table, an ORM can auto-load the order items when we fetch an order.

image

Micro ORMs

  • SQL auto-generated by ORMs can be inefficient.
  • Micro ORMs help mitigate this with more control over SQL and dynamic typing.

Document Databases

  • Allow you to represent an order as the order data PLUS order items.
  • Rarely need "joins"
  • Simplifies reading and updating individual items

Serverless Frameworks

  • Allows data access via "bindings"
  • Less boilerplate code to connect to DB
  • However, advanced data access scenarios have limited support

Related to Data Mapping Layer

Unit of Work pattern

  • Batch up all changes into a single atomic operation and update changes into DB
  • Prevents DB from going into invalid state.

CQRS - Command Query Responsibility Segregation

  • All the API endpoints either represent a command (write) or query (read)
  • Commands
  • Should only update data and not return any data
  • Queries
  • Should only return data and not update any data

💡 CQRS advocates the use of separate model objects for commands and queries and Make use of separate code paths for commands and queries.

  • i.e do not use the same objects to represent data access and Data Transfer Objects (DTOs)
  • This allows the database schema to change independently from public API of your microservice.

Table Module Pattern

💡 Domain Model pattern used one class per entity, e.g. one Order object per row in Orders DB. Table module uses one class per table, i.e. one Orders class containing all the domain logic for the Orders table.

  • Useful for batch operations.
  • May be used as in intermediate step when refactoring from Transaction Script pattern to Domain Model pattern.

From the Pluralsight course.

💡 Use multiple types of tests.

The Test Pyramid

Type of Test Features
UI Tests (a.k.a. End to End Tests) More integration, slower to run, less repeatable, less predictable
Exercise all μs when connected together.
Service-level tests (a.k.a. Integration Tests) Talk to real dependencies.
Needed to test code-paths not covered by unit-tests.
Unit Tests More isolation, faster to run, more repeatable, more predictable
Speedy because dependencies are mocked. May not test all code-paths

Unit Tests

  • Best suited for testing Business or Domain Logic.
  • Agree unit-tests with stakeholders.

💡 if you find a bug in the system, after fixing the bug, add a test-case for it.

Isolation

Why isolated? Because we only want to test business logic in isolation and not I/O, External Dependencies etc.

  • Replace external dependencies with mock.

Testability

If you want to be able to mock your code, your code should be written to enable dependency injection.

💡 Dependency Inversion: high-level dependencies shouldn't depend on low-level dependencies. Instead, both should depend on abstractions (e.g. interfaces).

Example of an untestable method: Unmockable dependencies: No way to test below method without calling DB.

image

Instead, create an interface and rewrite the code to depend on the interface method.

Tests can mock the interface with an implementation with a method that returns mock values.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment