From layers to vertical slices

Simplify your code and focus on your features

Jon Hilton

https://practicaldotnet.io

https://jonhilton.net

@jonhilt

Could you just... ?

@jonhilt

@jonhilt

@jonhilt

@jonhilt

@jonhilt

"We need to find Mr Anderson..."

@jonhilt

First Name Last Name(s) Last Slept Threat Level
Jon Hilton Today Harmless
Brian Blessed 2 Days Ago A bit loud
Not The One ? ?
The lady In the Red Dress Never Simulation

@jonhilt

?

ASP.NET/

Presentation

PersonController

ListAll

@jonhilt

ASP.NET

PersonController

ListAll

ListAll

PersonService

ListAll

PersonRepository

Business Logic

Data Access

@jonhilt

@jonhilt

@jonhilt

"Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer.

 

Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers."

Patterns of Enterprise Application Architecture by Martin Fowler et al

@jonhilt

@jonhilt

"adding this layer helps minimise duplicate query logic..."

Patterns of Enterprise Application Architecture by Martin Fowler et al

@jonhilt

"There are too many people to work through; we need a way to search"

@jonhilt

First Name Last Name(s) Last Slept Threat Level
The One And Only Today ?
Maybe The One 1 Hour Ago Always asleep
Not The One ? ?
Thomas Anderson Yesterday Presumably Harmless

Search term

Showing 4 of 8 results...

The one

@jonhilt

ASP.NET/

Presentation

PersonController

ListAll

ListAll

PersonService

ListAll

PersonRepository

Business Logic

Data Access

Search

Search

@jonhilt

Developers are LAZY...

(in a good way!)

Developers are LAZY...

People

https://www.behaviormodel.org/

ASP.NET/

Presentation

PersonController

ListAll

ListAll

PersonService

ListAll

PersonRepository

Business Logic

Data Access

Search

Search

@jonhilt

@jonhilt

"This search is broken; it keeps showing us agents as well as inhabitants.

Show us the agents separately!!"

@jonhilt

"Sentient programs. They can move in and out of any software still hardwired to the system. That means that anyone we haven't unplugged...is potentially an Agent. Inside the Matrix, they are everyone...and they are no one."

First Name Last Name Uptime
Agent Smith 6 years
Agent Jackson 2 years
Agent Thompson 3 months

Agent List

@jonhilt

PersonController

ListAll

ListAll

PersonService

ListAll

PersonRepository

Search

Search

AgentController

ListAll

AgentService

ListAll

@jonhilt

if(person.Uptime != null){
  // do stuff
}

if(person.FirstName == "Agent"){
  // do stuff
}

@jonhilt

PersonController

ListAll

ListAll

PersonService

PersonRepository

Search

AgentController

ListAll

AgentService

Add

Search

ListAll

Add

Add

ListAll

Destroy

Destroy

Destroy

Decommission

Decommission

@jonhilt

BIG classes

Lots o' references

  • Suddenly, people actually means Inhabitants (at service and controller level)
  • The person repository is used for both agents and inhabitants (are both, either, or non actually people?)
  • Conditional logic to figure out which "mode" we're in (destroying inhabitants or decommissioning agents)

 

Domain Model?

What Domain Model?

@jonhilt

Helpers

Mappers

Managers

Factories

ServiceFactories

Functions

Requests

Responses

Models

ViewModels

DALS

Readers

Repositories

ReadOnlyRepositories

FactoryFactories

Utilities

@jonhilt

Let's start over!

@jonhilt

"We need to find Mr Anderson..."

@jonhilt

?

Ask more questions

@jonhilt

Inhabitants

The Matrix

Agents

Computer programs

NEVER sleep

Crash occasionally

Good uptime

@jonhilt

First Name Last Name(s) Last Slept Threat Level
Jon Hilton Today Harmless
Brian Blessed 2 Days Ago A bit loud
Not The One ? ?
The lady In the Red Dress Never Simulation

Inhabitants

@jonhilt

@jonhilt

@jonhilt

?

ASP.NET/

Presentation

InhabitantController

ListAll

ASP.NET/

Presentation

InhabitantController

ListAll

ListAll

Request (query)

Response (model)

Inhabitants

@jonhilt

@jonhilt

@jonhilt

ASP.NET/

Presentation

InhabitantController

ListAll

ListAll

Request (query)

Response (model)

Inhabitants

Search

Search

Term (string)

SearchResults

@jonhilt

@jonhilt

@jonhilt

@jonhilt

ASP.NET/

Presentation

InhabitantController

ListAll

ListAll

Request

(command)

Inhabitants

Search

Search

Destroy

Destroy

Destroy Command!

Encapsulate by feature

InhabitantController

ListAll

ListAll

Search

Search

Destroy

Destroy

InhabitantController

InhabitantController

@jonhilt

Refactor

by feature

@jonhilt

Destroy

Inhabitant
Destroy()

public void Destroy(){	
    ThreatLevel = "Extinguished";
    Expired = DateTime.Now;
    
    DomainEvents.Raise(new InhabitantDestroyedEvent
      { 
          InhabitantId = Id,
          DestroyedOn = Expired
      });    
}

Handler

Aggregate

Extend and Refactor

Handling Side Effects

Avoid links between features

(specific, business logic)

@jonhilt

PilotController

List

Pilot

Search

Retire

List

Search

Retire

AttendantController

ListBy
Rank

Add

Update
Address

List

Add

Update
Address

Flight

Attendant

Address

@jonhilt

Pilot (aggregate)

Dapper

Query

Dapper

Query

Dapper

Query

Dapper

Query

EF Core

public class Flight
{
    public List<Guid> Attendants { get; private set; }
    public Guid Pilot { get; private set; }
    public Guid CoPilot { get; private set; }
    
    public DateTime Departed { get; private set; }
    
    public void AssignAttendant(Guid Id)
    {
        this.Attendants.Add(Id);
    }
    
    public void Depart()
    {
        if (Pilot == null || CoPilot == null)
            throw new InsufficientFlightCrewException();
            
        Departed = DateTime.Now();
    }
}

Missing Models

What about tests?

@jonhilt

PersonController

ListAll

ListAll

PersonService

PersonRepository

Search

AgentController

ListAll

AgentService

Add

Search

ListAll

Add

Add

ListAll

Destroy

Destroy

Destroy

Decommission

Decommission

@jonhilt

PersonController

PersonService

PersonRepository

Destroy

Destroy

Destroy

Test?

Test in isolation

Test?

PersonController

Destroy

Destroy

Inhabitants

Unit Tests

  • Great for business logic
  • Can test aggregates
  • (no side effects!)

PersonController

Destroy

Destroy

Inhabitants

When destroying an inhabitant

They should be completely expunged from the system

Integration/Scenario Tests

The names used are those of the business domain (feature)

@jonhilt

@jonhilt

5 Practical Tips

  1. Understand what you're trying to build

  2. Avoid separating by layers or technical concerns

  3. Embrace Command Query Separation

  4. Leverage MediatR (start your features off on the right foot)

  5. Refactor on a per feature basis

  6. (BONUS) Use Domain Events to decouple parts of your system

There is no "best" design.

There is only the "best design given our current understanding"

Jimmy Bogard -  "Strengthening your domain"

https://bit.ly/NoBestDesign

Examples in the wild

Humanitarian Toolbox - AllReady

Jimmy Bogard - Contoso University

https://github.com/jbogard/ContosoUniversityDotNetCore

Tackling Business Complexity in a Microservice with DDD and CQRS Patterns

@jonhilt

jonhilton.net/slices

 

jon@jonhilton.net

@jonhilt

From layers to vertical slices

By jonhilt

From layers to vertical slices

  • 871