1. What is Architecture?
- Layers & Access: In softwareontwikkeling verwijst architectuur naar meer dan alleen hoe de code is gestructureerd of georganiseerd. Het beschrijft de choreografie van hoe verschillende lagen binnen een systeem met elkaar interactie hebben en hoe informatie door deze lagen stroomt. Wanneer je de toegang tussen deze lagen afbakent, ontstaat er een architectuur.
- Principles & Architecture: Hoewel architectuur vaak ontwerpprincipes zoals SOLID omvatten en verschillende ontwerppatronen omarmen, vormen deze elementen op zichzelf geen architectuur.
Echter: leidende principes en patronen zijn onvermijdelijk voor een robuuste architectuur. Deze patronen zijn slechts een hulpmiddelen die helpen bij het realiseren van een bredere structurele visie. Ze vormen op zichzelf niet de architectuur.
Doel van architectuur? = Separation of concerns
Waarom? Het maakt het systeem beter uitbreidbaar, onderhoudbaar en makkelijker voor nieuwe medewerkers om zich in te werken. Bovendien, als een applicatie het separation of concerns principe volgt, zijn de kosten van iteraties (de tijd die nodig is om een nieuwe functie toe te voegen) lager doordat de codebasis gestructureerd is en de componenten ervan ontkoppeld zijn.
2. Quest for an architecture
2.1 Basic Architecture
Je maakt een leeg project, maakt wat directories aan en structureert zo je bestanden. Geen libraries, lagen of goed gedefinieerde regels voor het splitsen van verantwoordelijkheden. Gewoon zorgen dat het werkt. Perfect voor Proof of Concepts (POC) of demo-applicaties.
Je kunt kiezen voor een basisarchitectuur als je zo snel mogelijk een MVP (Minimum Viable Product) wilt lanceren en je geen extra tijd en middelen wilt besteden aan het leren van principes van andere architectuurmodellen. Kies altijd voor separation of concerns. Ontwikkel geen slechte gewoontes als programmeur! Je code is kunst, maak er dan ook een kunstwerk van.
2.2 N-Tier Architecture

= het verdelen van je code in lagen (tiers), waarbij elke laag een afzonderlijk aandachtsgebied beheert.
De “N” van n-tier geeft aan dat je zoveel lagen kunt hebben als je wilt, bijvoorbeeld 3-tier, 4-tier (Sidenote: sommigen zeggen dat het staat voor Neuropsychopharmacologie).
Presentatielaag: dit is de interface die door de cliënt wordt gebruikt om met uw applicatie te communiceren (client-facing-layer). Het kan verschillende vormen aannemen, van mobiele of desktopapplicaties tot een API of een command line.
Bijvoorbeeld: als u een aparte applicatie voor de UI heeft, gebouwd met Angular of React bijvoorbeeld, die communiceert met een backend-API, dan zal de API zelf uw presentatielaag zijn (U behandelt de Angular-app als client, aangezien het een volledig andere applicatie is).
- Presentatie
- Het roept een business logic laag aan, die zorgt voor de verwerking van verzoeken;
- Alleen zaken die direct verband houden met de presentatie (API-configuraties, routes, …) bevinden zich hier;
- Laat de bedrijfslogica niet naar deze laag doordringen, houd het schoon van bedrijfsregels !
- Business Logic Layer (BLL)
- De Business Logic van uw applicatie bevindt zich hier, samen met alles behalve de ORM en andere zaken die betrekking hebben op het verkrijgen van gegevens, omdat ze deel uitmaken van uw volgende laag => DAL;
- Omvat: services die worden gebruikt voor communicatie met externe systemen, zoals het verzenden van een e-mail, het pushen van een bericht naar een topic en dergelijke;
- Als de BLL gegevens moet opslaan, ophalen, bijwerken of verwijderen zal deze de DAL aanspreken.
- Data Access Layer (DAL)
- Theoretisch gezien: het weet niets van andere lagen en kan op elk moment worden gewijzigd met een andere implementatie;
- Vanwege de afhankelijkheid van de BLL van DAL, zal de Business Logic worden beïnvloed en kunnen wijzigingen zich naar boven verspreiden, en dat is een beetje vreemd omdat alleen de business het BLL zou moeten, kunnen wijzigen;
- Het wijzigen van de databaseprovider wordt niet bepaald door de business, het is een infrastructurele beslissing.
Let op de afhankelijkheden: presentatie => BLL => DAL.
Onze hele applicatie is afhankelijk van DAL als de kern van het systeem (= Database Driven Design). Het lijkt niet echt een goed idee om deze applicatie afhankelijk te maken van DAL.
Uitgaande van het idee dat onze applicatie een business probleem oplost, zou het dan niet beter zijn om de BLL te verheffen en deze de kern van ons systeem te maken en het onafhankelijk te maken van andere lagen?
2.3 Hexagonal Architecture

= BLL is de kern. Binnen de BLL definiëren we interfaces die worden geïmplementeerd door andere externe componenten.
Voorbeeld: Abstracties worden vastgelegd in de BLL (Irepository). Implementaties worden geleverd door de componenten (in dit geval Data Access).
Dependency Inversion & Dependency Injection helpen ons om de implementatie van Irepository op runtime aan te bieden en zo te voorkomen dat DA wordt genoemd tijdens het compileren. Nul verwijzingen naar andere componenten van onze applicatie. Het is afhankelijk van niets behalve de BL.
Haal alle andere afhankelijkheden van externe systemen eruit (vb.: IEmailService, IFileManager, …). Abstraheer elke afhankelijkheid van een ander systeem door een interface binnen de BL te hebben, en de implementatie ervan als onderdeel van een ander component.
In deze context wordt de interface gedefinieerd door BL, een Poort genoemd en de implementatie die door de andere componenten wordt geleverd, Adapter (daarom vind je in sommige documentatie de Hexagonale architectuur genoemd als Poorten en Adapters). Primaire adapters (die in de BL roepen) en secundaire adapters (die op runtime worden aangeroepen door de BL, zoals DA, E-mail). De presentatielaag uit het N-tier model wordt vertegenwoordigd door primaire adapters in de context van het Hexagonale model. Ze fungeren als een ingress-controller en zijn cliëntgerichte adapters (API, UI, Command Line). Secundaire adapters zijn degene die door de BL worden aangeroepen en communicatie met externe systemen afhandelen.
• Primaire adapters drijven – ze doen oproepen naar de BL
• Secundaire adapters worden gedreven – BL roept ze op vanwege omgekeerde controle
Dit noemen we een Domain Driven Design (DDD).
Probleem: dat is cool, maar tientallen componenten hebben is eigenlijk niet het beste idee. Laten we een manier vinden om ze te groeperen!
2.4 Onion Architecture

= omdat het de ui-structuur nabootst. De lagen gaan rondom de kern. Dependencies gaan naar binnen – en van de buitenste lagen naar de binnenste. Het hele idee van de Onion structuur is om de Business Logic in de kern te plaatsen, en andere lagen ervan afhankelijk te maken door gebruik te maken van Dependency Inversion & Dependency Injection.
Twee bijzonderheden:
- Primaire adapters (client-facing-ones) maken nu deel uit van de presentation layer, terwijl de secundaire adapters (die communiceren met externe systemen) deel uitmaken van de infrastructure layer.
- De Business Logic (BL) is nu verdeelt in N-lagen. N omdat het aantal lagen per geval kan variëren. De kern wordt vertegenwoordigd door een domain layer. Deze laag heeft geen dependencies en bevat business models, entities, value objects, enumerations en andere business related objects (het volgen van DDD-principles is een good practice voor een goed ontwerp van de domainlaag). De lagen ertussen kunnen variëren. Gewoonlijk zou je Domain Services (een ander concept van DDD) in een aparte laag rond het domain plaatsen, met enkele ports (interfaces) waarvan de implementatie zich in de buitenste infrastructuurlaag bevindt – (Als je CQRS zou volgen, kunnen command & query hun eigen laag rond Domain Services hebben en als je het Use-Case-Pattern gebruikt, zou je deze ook weer in een aparte laag plaatsen enz). Er zijn geen strikte regels over hoeveel lagen je kunt hebben.
2.5 Clean Architecture

Voegt een beetje regulering toe aan de onion structuur. Het stelt dat de domain layer bedoeld is voor enterprise-wide business rules die het minst waarschijnlijk zullen veranderen (entities, enumerations, value objects, core domain objects). Daaromheen bevindt zich de application layer, die alles omvat dat te maken heeft met hoe de business opereert. Ook is dit waar de ports (interfaces) zich bevinden. De infrastructure layer bevat adapters (implementaties) die fungeren als gateways naar externe systemen.
KEY Principles:
- Dependency Rule: dependencies moeten altijd naar binnen wijzen, richting de kern van de business logic. Binnenste lagen mogen niet afhankelijk zijn van de buitenste lagen.
- Separation of Concerns: elke laag heeft een specifieke verantwoordelijkheid en de zorgen van de ene laag mogen niet doorsijpelen naar een andere.
Deze scheiding helpt bij het behouden van modulariteit en testbaarheid. - Independence of Frameworks: de kern van de business logic mag niet afhankelijk zijn van de specifieke frameworks of tools. Frameworks en externe tools moeten zich aanpassen aan de applicatie en niet andersom.
- Kan leiden tot betere onderhoudbare, schaalbare en testbare code.
PROS:
- Modularity and Maintainability
Voordeel: Clean Architecture bevordert modulariteit door verantwoordelijkheden in afzonderlijke lagen te scheiden. Dit maakt de codebasis beter onderhoudbaar en maakt het gemakkelijker om updates of wijzigingen aan specifieke componenten door te voeren, zonder de gehele applicatie te beïnvloeden. - Testablity
Voordeel: separation of Concerns vergemakkelijkt unit testing. De Business Logic in de kern laag kan onafhankelijk van external dependencies worden getest, wat leidt tot robuustere en betrouwbaardere tests. - Independence of Frameworks
Voordeel: de kern van de business logic is niet tightly coupled aan specifieke frameworks of libraries. Deze onafhankelijkheid maakt het gemakkelijker om frameworks te wisselen of te upgraden zonder de kernfunctionaliteiten te beïnvloeden. - Flexibility
Voordeel: Clean Architecture biedt flexibiliteit bij het kiezen van technologieën voor verschillende lagen.
Bijvoorbeeld: je kunt wisselen van verschillende web frameworks, databases of UI frameworks zonder grote wijzigingen in de kern van de business logic. - Scalability
Voordeel: de modulaire structuur van Clean Architecture zorgt voor betere scalability. Het is gemakkelijker om verschillende delen van de applicatie onafhankelijk te scalen, en teams kunnen werken aan specifieke lagen zonder anderen te hinderen.
CONS:
- Complexity
Nadeel: het implementeren van Clean Architecture kan extra complexiteit introduceren, vooral in de beginfase van de ontwikkeling. De scheiding van verantwoordelijkheden kan leiden tot meer bestanden en mappen, wat overweldigend kan zijn voor kleinere projecten. - Learning Curve
Nadeel: Developers die nieuw zijn met Clean Architecture, kunnen te maken krijgen met een leercurve. Het begrijpen van de principes en het correct implementeren van de architectuur kan tijd en moeite kosten. - Boilerplate code
Nadeel: Clean Architecture kan het schrijven van meer boilerplate-code met zich meebrengen, met name bij het mappen van gegevens tussen lagen. Dit kan leiden tot een langere ontwikkeltijd, hoewel wordt beweerd dat de voordelen op het gebied van onderhoudbaarheid opwegen tegen dit nadeel. - Over-Engineering for simple projects
Nadeel: Clean Architecture kan overdreven zijn voor eenvoudige of kleine projecten waar de voordelen van separation of concerns en modulariteit niet zo duidelijk naar voren komen. In dergelijke gevallen is een eenvoudigere architectuur meer geschikt. - Performance overhead
Nadeel: de extra lagen en abstracties in Clean Architecture kunnen enige performance overhead introduceren. In applicaties waar performance van cruciaal belang is, kunnen zorgvuldige overwegingen en optimalisatie noodzakelijk zijn.
Conclusie:
Voordelen op het gebied van onderhoudbaarheid, testbaarheid en flexibiliteit. Maar het is essentieel om deze voordelen af te wegen tegen mogelijk nadelen, vooral in de context van de specifieke omvang en complexiteit van het project. Het is geen one-size-fits-all oplossing, en Developers moeten afwegingen overwegen op basis van de vereisten en beperkingen van het project.
De Essentie:
Clean Architecture is niet zomaar een andere architectuurstijl; het is een filosofie, een diepgaande benadering om systemen te ontwerpen met prioriteit voor onderhoudbaarheid en schaalbaarheid. Het hart van Clean Architecture wordt gevormd door de business regels – onbevlekt en puur – met externe functionaliteiten, zoals databases en UI aan de rand, ervoor zorgend dat wijzigingen in het ene onderdeel de andere niet negatief beïnvloed.
Clean Architecture pleit voor een principe gedreven benadering boven een autoritaire, waarbij softwareontwikkelaars worden aangemoedigd om de essentie ervan te internaliseren om daaruit veerkrachtige systemen te creëren.
3. Common mistakes and pitfalls
3.1 Entity: Misunderstanding & clarifications
Zowel Clean Architectrure als DDD gebruiken de term “Enity” in software design. Toch verschillen ze van elkaar. In Clean Architecture encapsulaten de entiteiten cruciale business logic en worden ze puur gehouden, dat wil zeggen vrij van externe invloeden zoals database. In DDD benadrukken entiteiten het belang van een unieke identiteit binnen de begrensde context en zijn ze in staat om rijker gedrag en zelfs complexe relaties met andere entiteiten te encapsuleren.
Veel gemaakte fouten & hun gevolgen
Veel ontwikkelaars, voor diegenen die bekend zijn met Object-Relation Mapping (ORM) frameworks, zien entiteiten als eenvoudige datastructuren die database records weerspiegelen. Deze perceptie leidt hen ertoe om Anemic Entities te maken. Anemic Entity: objecten die geen gedrag bevatten en slechts dienen als een verzameling van getters en setters.
Waarom is dit verkeerd?
Vermindert de cohesie: Anemic Entities worden losgekoppeld van de bewerkingen die erop worden uitgevoerd. In tegenstelling hiermee streven rich entities in Clean Architecture ernaar om gegevens en relevante bewerkingen samen te groeperen, wat zorgt voor een hogere cohesie.
Hier is de operatie applyDiscount losgekoppeld van de OrderAnemic-class en in een service geplaatst. Dit vermindert de cohesie omdat de gegevens (attributes van orderAnemic) en de bewerkingen op de gegevens (applyDiscount) gescheiden zijn.
Violates Persistense Agnosticism:
De kern van de business logic wordt impliciet gekoppeld aan een specifieke persistence mechanism door entiteiten te creëren die rechtstreeks de databasestructuren weerspiegelen. Dit staat in contrast met het principe van Clean Architecture om de kernlogica te scheiden van externe details.

Het attribuut databaseRowVersion is rechtstreeks gekoppeld aan een veelgebruikte databasestructuur dat gebruikt wordt voor optimistic concurrency. Door dit in onze entiteit op te nemen, koppelen we de entiteit aan een specifiek persistence mechanism.
Verdunt de Business Logic: in plaats van de business logic binnen de entiteiten zelf te centraliseren verspreiden anemic entities de logica vaak over services of controllers, waardoor het systeem moeilijker te begrijpen en onderhouden is.
Entity’s Value Judgement:
Het is cruciaal om te verduidelijken dat ik niet de anemic entities veroordeel. Ze vinden hun plaats in specifieke scenario’s, vooral in gestroomlijnde applicaties met minder ingewikkelde domain logic of wanneer ze gecombineerd worden met bepaalde frameworks. Het belangrijkste is om het verschil te herkennen en een weloverwogen keuze te maken. Als je kiest voor een anemic entity, zorg er dan voor dat dit komt omdat het tastbare waarde toevoegt aan je scenario, en niet alleen als een standaardkeuze of een overgenomen gewoonte. In software architectuur, zoals in zoveel domeinen, is context van het grootste belang.
3.2 Application Business Rules vs Enterprise Business Rules

De fundamentele kracht van Clean Architecture ligt in de duidelijke afbakening van verantwoordelijkheden, vooral als we het hebben over applicatie Business Rules en Enterprise Business Rules. Toch worden door veelvoorkomende misvattingen hun grenzen vaak vervaagd, wat leidt tot ontwerpfouten. Clean Architecture maakt het onderscheid tussen applicatie- en business logica. De eerste richt zich op de specifieke applicatievoorwaarden, waarbij vaak meerdere entiteiten of externe factoren betrokken zijn. De laatste draait om universele waarheden over entiteiten, die niet beïnvloed worden door externe omstandigheden. Het herkennen van deze verschillen is essentieel voor een veerkrachtig en aanpasbaar systeem.
3.2.1 Enterprise Business Rules: Heart of the Entity

Neem de entiteit ‘Creditcard’ in overweging. Op dit niveau zijn we geïnteresseerd in de inherente eigenschappen en gedragingen van de creditcard zelf, ongeacht externe factoren of specifieke applicatievoorwaarden. Hier zouden we:
• Het kaartnummerformaat valideren;
• De vervaldatum controleren;
• Het formaat van de beveiligingscode verifiëren.
Alle operaties draaien uitsluitend om de creditcard-entiteit, waarbij de focus ligt op de intrinsieke eigenschappen of gedragingen. We hebben te maken met universele waarheden over wat een geldige creditcard vormt, zonder beïnvloeding door externe omstandigheden of specifieke applicatiescenario’s.
3.2.2 Application Business Rules (Use Cases)
Een laag hoger vinden we de Use Cases dat onze Application Business rules orkestreert. Hier wordt de logica gestuurd door specifieke toepassingsvoorwaarden en scenario’s, vaak met betrokkenheid van meerdere entiteiten of externe factoren.
Met hetzelfde creditcard voorbeeld zouden we hier:
• Controleren of de gebruiker die de kaart aanvraagt minstens 18 jaar oud is;
• Het land van de aanvrager bepalen en regio specifieke regels of aanbiedingen toepassen;
• Overwegen om te integreren met externe services om bv kredietcontroles uit te voeren.

Hier wordt het service genoemd maar in Clean Architecture spreken ze meestal (en benoemen) van Use Cases.
Deze laag gaat verder dan de grenzen van een enkele entiteit. Het orkestreert interacties, volgt processen op en past contextuele business logic toe die uniek kunnen zijn voor een bepaalde toepassing of scenario. Het is waar de bredere business logic die relevant is voor de specifieke toepassing tot leven komt.
3.3 Common Mistakes
- Blurring the boundaries: Developers verwarren vaak Application Business Rules met allesomvattende waarheden, die de Enterprise Business Rules overschaduwen. Deze misvatting wordt verergerd door een misvatting over entiteiten, die idealiter meer in lijn zouden moeten zijn met onveranderlijke feiten van Enterprise Business Rules.
- Waarom dit fout is:
Ontbreekt aan flexibiliteit: Het behandelen van Application Business Rules als constante waarheden belemmert de aanpasbaarheid van het systeem aan toekomstige veranderingen.
Risico’s voor integriteit: Het niet onderscheiden van het verschil kan leiden tot onbedoelde schendingen van kern business logic tijdens wijzigingen in de applicatie. - Net als onze voorbeeldcode hierboven, het rechtstreeks toevoegen van de leeftijdscontrole van de gebruiker aan de creditcard class zou een fout zijn. Dit zou een ongepaste vermenging zijn van de Application Business Rules (leeftijdscontrole voor het aanvragen van een creditcard) met Enterprise-regels (wat een creditcard geldig maakt). En in een eenvoudige context: Het creditcard-model geeft niet om de leeftijd van de gebruiker.
- Waarom dit fout is:
- Entities crossing boundaries: vanwege de foutieve overtuiging dat entiteiten alle bedrijfsregels omvatten, kunnen Developers ze rechtstreeks uit controllers uitvoeren, waardoor entiteiten grenzen overschrijden waar ze dat niet zouden moeten doen.
- Waarom dit fout is:
Breach of Separtion: het rechtstreeks aanroepen van entiteiten vanuit controllers ondermijnt het principe van Separation of Concerns.
Dependency Rule Violation: deze aanpak schendt de regel dat buitenste lagen afhankelijk moeten zijn van de binnenste en niet andersom.
- Waarom dit fout is:
- Misinterpreting Control Flow: een andere valkuil is het lezen van pijlen in het Clean Architecture diagram als control flow indicatoren in plaats van als dependencies.
- Waarom dit fout is:
Control Flow Inversion: hoewel de afhankelijkheden naar binnen wijzen, verloopt de werkelijke controlestroom van buitenste lagen (zoals controllers) naar binnen en weer naar buiten.
Design Flaws: Een verkeerde interpretatie van de control flow kan leiden tot ontwerpkeuzes die de aanpasbaarheid en schaalbaarheid compromitteren. - Bij de incorrecte aanpak is de entiteit verweven met externe services, waardoor het systeem moeilijker te testen, begrijpen en onderhouden is. Het schendt ook het principe van dependency inversion omdat de kern entiteit nu afhankelijk is van externe details. Dit is precies het soort ontwerpfout dat Clean Architecture wil voorkomen.
- Waarom dit fout is:
Heb je ooit het gevoel gehad verstrikt te raken in een web van code, niet wetend welk draadje je moet volgen? Clean Architecture is het kompas dat je uit het labyrint leidt. Het gaat niet om het platform … het gaat om de zuiverheid van het ontwerpen. Clean Architecture is niet zomaar een methode; het is een beweging (en het roept naar ontwikkelaars zoal jullie die hongerig zijn naar duidelijkheid en uitmuntendheid).
Geschreven door: Ides VH.