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
-
Understand what you're trying to build
-
Avoid separating by layers or technical concerns
-
Embrace Command Query Separation
-
Leverage MediatR (start your features off on the right foot)
-
Refactor on a per feature basis
-
(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
- 972