Wat is het structureren van een project?

Het structureren van een project houdt in dat je een logische en consistente indeling kiest voor de code en bestanden binnen een softwareproject. Dit helpt bij het organiseren van de code, het verbeteren van de leesbaarheid en het vereenvoudigen van onderhoud en samenwerking binnen een team.

Bij het structureren van een project bepaal je hoe de verschillende onderdelen van de applicatie worden gegroepeerd, zoals domeinlogica, services, API-endpoints en database-interacties. Dit kan gebeuren op basis van architectuurprincipes, best practices en de behoeften van het project.

Een goede projectstructuur zorgt voor:

  • Scheidbaarheid: Componenten zijn duidelijk gescheiden, waardoor code makkelijker te begrijpen en te testen is.
  • Schaalbaarheid: Het project kan eenvoudig groeien zonder dat het chaotisch wordt.
  • Onderhoudbaarheid: Het is makkelijk om fouten op te sporen en wijzigingen door te voeren.
  • Samenwerking: Teamleden kunnen efficiënter samenwerken en vinden snel wat ze nodig hebben.

Casus

Tom, een softwareontwikkelaar, kreeg de taak om een nieuwe functie te testen voor een intern project. Voordat het team besloot om de functie volledig te integreren, moest Tom eerst een Proof of Concept (PoC) bouwen. Het doel was simpel: een kleine, werkende versie maken om te zien of het idee technisch haalbaar was.

Omdat het slechts een PoC was, koos Tom voor een eenvoudige projectstructuur. Hij maakte een C# Console Application met een strakke indeling:

MyPoC/
│── MyPoC.csproj
│── Program.cs           (Hoofdprogramma)
│── Services/
│   ├── IDataService.cs  (Interface)
│   ├── DataService.cs   (Implementatie)
│── Models/
│   ├── User.cs          (Domeinmodel)
│── Tests/
│   ├── DataServiceTests.cs (Unit test)

Met deze structuur hield hij de code overzichtelijk en gescheiden, zelfs in deze kleine testfase.

Na een paar dagen testen bleek dat het idee werkte! Nu moest de functie in de bestaande applicatie worden opgenomen. Gelukkig had Tom de code netjes gescheiden.

  • Domeinlogica (Models/) bleef ongewijzigd, want het domein was goed neergezet in models.
  • De service (Services/) kon eenvoudig worden verplaatst naar de grotere applicatie, omdat deze los stond van de interface.
  • De tests (Tests/) hoefden nauwelijks te worden aangepast, wat tijd bespaarde.

Omdat Tom een modulaire aanpak had gekozen, kon het team de PoC snel en zonder rommel integreren in de echte applicatie.

Hoe zit het structureren van een project in elkaar?

Bij het structureren van een project wordt rekening gehouden met een aantal factoren:

Grootte en complexiteit

De grootte en complexiteit van een project bepalen in grote mate de structuur ervan. Kleine projecten of Proof of Concepts (PoC) hebben vaak een eenvoudige opzet met één project en een minimale mappenindeling. Bij grotere of complexere projecten is een modulaire aanpak met meerdere lagen, zoals MVC of een hexagonale architectuur, gebruikelijk.

Teamgrootte en samenwerking

Ook de teamgrootte en samenwerking spelen een belangrijke rol. In een klein team werkt een platte en eenvoudige structuur het beste, zodat ontwikkeling snel kan verlopen. In grotere teams is een strikte scheiding van verantwoordelijkheden essentieel, waarbij duidelijke lagen en domeinen helpen om het overzicht te behouden.

Programmeertaal en framework

Daarnaast beïnvloeden de programmeertaal en het gekozen framework de structuur van een project. C#-toepassingen volgen vaak een MVC-structuur voor webapplicaties of hanteren een Clean Architecture voor complexere systemen. React- en Vue-projecten zijn meestal gebaseerd op een component-gebaseerde structuur.

Onderhoudbaarheid en schaalbaarheid

Onderhoudbaarheid en schaalbaarheid zijn eveneens belangrijke overwegingen. Een modulaire structuur maakt het eenvoudiger om nieuwe functionaliteit toe te voegen of bestaande onderdelen te vervangen. Daarnaast zorgt een duidelijke scheiding van code ervoor dat wijzigingen op één plek geen onverwachte gevolgen hebben voor andere delen van het project.

Type applicatie

Het type applicatie bepaalt ook hoe de structuur wordt ingericht. Voor webapplicaties worden vaak architecturen zoals MVC, API-first of microservices toegepast. Desktopapplicaties volgen meestal het MVVM-patroon, terwijl command-line tools doorgaans een eenvoudige service- en commandostructuur hebben.

Testbaarheid

Tot slot is testbaarheid een belangrijke factor bij het kiezen van een projectstructuur. Unit tests en integratietests vereisen een duidelijke scheiding van verantwoordelijkheden, bijvoorbeeld door gebruik te maken van Dependency Injection. Een modulaire opzet maakt het eenvoudiger om onderdelen te mocken en tests geïsoleerd uit te voeren, wat bijdraagt aan een stabieler en beter onderhoudbaar project.

Voorbeeld projectstructuur voor een kleine PoC

Hierna volgt een uitwerking van een aantal voorbeelden van een kleine eenvoudige projectstructuur naar een uitgebreidere projectstructuur met een modulaire opzet. Steeds wordt uitgelegd hoe de structuur in elkaar zit en een aantal overwegingen. De overwegingen zijn niet volledig en uitputtend.

Een eenvoudig PoC kan worden opgezet met een eenvoudige projectstructuur die alleen de applicatie scheidt van de testen in een mappenstructuur.

MySolution.sln
│── MyProject/
│   ├── MyProject.csproj        (Projectbestand)
│   ├── Program.cs              (Hoofdprogramma)
│   ├── Services/
│   │   ├── IUserService.cs     (Service interface)
│   │   ├── UserService.cs      (Service implementatie)
│   ├── Models/
│   │   ├── User.cs             (Model/Entiteit)
│   ├── Tests/
│   │   ├── UserServiceTests.cs (Unit tests)
│   ├── MyProject.sln           (Solution bestand)

We hebben een C# Solution opgezet met één project, waarin zowel de applicatiecode als de testen zijn ondergebracht. Deze aanpak is minimalistisch en daarmee geschikt voor kleine applicaties of proof of concepts. Naarmate de code groeit, kan de oplossing eenvoudig worden uitgebreid naar meerdere projecten.

Voorbeeld projectstructuur voor een kleine eenvoudige applicatie

Voor een eenvoudige applicatie kan een C# Solution met één hoofdproject voor de applicatiecode en een apart testproject voor het testen van de functionaliteit worden opgezet. Dit zorgt voor een overzichtelijke structuur waarin productcode en testcode duidelijk gescheiden blijven.

MySolution/
│── MySolution.sln                 (Solution-bestand)

├── MyApp/                          (Hoofdproject)
│   ├── MyApp.csproj                (Projectbestand)
│   ├── Program.cs                  (Startpunt van de applicatie)
│   ├── Services/
│   │   ├── ICalculatorService.cs    (Interface)
│   │   ├── CalculatorService.cs     (Implementatie)
│   ├── Models/
│   │   ├── CalculationResult.cs     (Domeinmodel)
│   ├── Controllers/
│   │   ├── CalculationController.cs (API Controller)

├── MyApp.Tests/                     (Testproject)
│   ├── MyApp.Tests.csproj           (Projectbestand voor testen)
│   ├── Services/
│   │   ├── CalculatorServiceTests.cs (Unit tests voor CalculatorService)
│   ├── Controllers/
│   │   ├── CalculationControllerTests.cs (Unit tests voor Controller)

Om een beter beeld te kunnen vormen bij dit voorbeeld, zal hieronder per project een korte toelichting worden gegeven.

Hoofdproject (MyApp/) Dit bevat de kernfunctionaliteit van de applicatie, zoals de business logic, modellen en controllers. Hier wordt de applicatie daadwerkelijk uitgevoerd.

Testproject (MyApp.Tests/) Dit is een apart project dat is gewijd aan het testen van de code in MyApp/. Het bevat unit tests en eventuele integratietests. Hierdoor kan de productcode schoon en onafhankelijk blijven van testgerelateerde code.

Voorbeeld projectstructuur voor een schaalbaar project met Clean Architecture

De volgende logische stap zou zijn om de code verder te scheiden in meerdere projecten binnen dezelfde solution.

In Clean Architecture wordt de applicatie opgedeeld in vier lagen. De Entities (domeinlaag) bevatten de pure businesslogica en modellen, zonder enige afhankelijkheden van externe systemen. De Use Cases (applicatielaag) coördineren de businessregels en voeren de interacties uit die de applicatie nodig heeft. De Infrastructure (infrastructuurlaag) is verantwoordelijk voor zaken zoals de database, externe API’s en logging. Ten slotte is er de Presentation (presentatielaag), die de applicatie exposeert via een web API, CLI, of een user interface.

MySolution/
│── MySolution.sln                    (Solution-bestand)

├── MyApp.Presentation/                (Presentatielaag - bevat Views)
│   ├── MyApp.Presentation.Web/        (MVC of Blazor Web-project)
│   │   ├── MyApp.Presentation.Web.csproj
│   │   ├── Program.cs
│   │   ├── Startup.cs
│   │   ├── Pages/                      (Voor Razor Pages)
│   │   │   ├── Index.cshtml
│   │   │   ├── Calculation.cshtml
│   │   ├── Views/                      (Voor MVC)
│   │   │   ├── Shared/
│   │   │   │   ├── _Layout.cshtml
│   │   │   ├── Home/
│   │   │   │   ├── Index.cshtml
│   │   ├── Components/
│   │   ├── Controllers/                (Indien MVC wordt gebruikt)
│   │   │   ├── CalculationController.cs
│   │   ├── wwwroot/                     (Statische bestanden)
│   │   │   ├── css/
│   │   │   ├── js/

├── MyApp.Application/                 (Use Case-laag)
│   ├── MyApp.Application.csproj
│   ├── Interfaces/
│   │   ├── ICalculatorService.cs
│   │   ├── ICalculationRepository.cs
│   ├── UseCases/
│   │   ├── PerformCalculation.cs

├── MyApp.Domain/                      (Domeinlaag - Zuivere businesslogica)
│   ├── MyApp.Domain.csproj
│   ├── Entities/
│   │   ├── CalculationResult.cs

├── MyApp.Infrastructure/               (Infrastructuurlaag)
│   ├── MyApp.Infrastructure.csproj
│   ├── Persistence/
│   │   ├── AppDbContext.cs
│   │   ├── CalculationRepository.cs
│   ├── ExternalServices/
│   │   ├── ThirdPartyApiClient.cs

├── MyApp.Tests/                        (Testproject)
│   ├── MyApp.Tests.csproj
│   ├── Application/
│   │   ├── PerformCalculationTests.cs
│   ├── Infrastructure/
│   │   ├── CalculationRepositoryTests.cs

Clean Architecture samengevat

LaagVerantwoordelijkheid
Presentation (UI)Interactie met de gebruiker (MVC, Blazor, CLI, etc.).
Application (Use Cases)Bevat alle businesslogica en orchestration (bijv. PerformCalculation).
Domain (Entities)Puur domeinmodel, zonder afhankelijkheden. Alleen entiteiten en value objects.
InfrastructureImplementaties voor persistence, externe services, logging, etc.

Voordelen van Clean Architecture

  1. Scheidbaarheid van verantwoordelijkheden
    Clean Architecture zorgt voor een duidelijke scheiding van verantwoordelijkheden tussen lagen. Dit maakt de applicatie gemakkelijker te begrijpen, onderhouden en uitbreiden, omdat iedere laag een specifieke taak heeft (bijv. de presentation-laag behandelt de UI en de application-laag de businesslogica).

  2. Testbaarheid
    Doordat de businesslogica (in de application-laag) gescheiden is van de infrastructuur (zoals databases en externe API’s), wordt het eenvoudiger om unit tests te schrijven voor de use cases zonder afhankelijkheden van de infrastructuur.

  3. Schaalbaarheid
    De architectuur is zeer schaalbaar. Het maakt het mogelijk om nieuwe functionaliteit toe te voegen door simpelweg nieuwe use cases te introduceren in de application-laag of nieuwe entiteiten in de domain-laag, zonder dat andere lagen geraakt worden.

  4. Flexibiliteit
    Omdat de infrastructuurlaag (zoals databases en externe diensten) aan de buitenkant van de applicatie ligt, kan de implementatie ervan eenvoudig worden vervangen zonder de businesslogica te beïnvloeden. Dit maakt het mogelijk om bijvoorbeeld de database of externe APIs te wisselen zonder veel code te herstructureren.

  5. Onafhankelijkheid van frameworks
    De applicatie is niet afhankelijk van specifieke frameworks of technologieën. Je kunt bijvoorbeeld makkelijk overstappen van een MVC-webapplicatie naar een API-gebaseerde applicatie, zonder de kernlogica opnieuw te hoeven schrijven.


Nadelen van Clean Architecture

  1. Complexiteit voor kleine projecten
    Voor kleinere applicaties of proof of concepts (PoC’s) kan Clean Architecture te complex zijn. Het vereist meer mappen, lagen en structuur, wat het eenvoudiger maakt voor grotere projecten, maar de setup voor een klein project kan onnodig ingewikkeld zijn.

  2. Overhead van abstracties
    De toepassing van abstracties (zoals interfaces en dependency injection) kan leiden tot een hogere complexiteit en meer code die onderhoud vereist. Dit kan de snelheid van ontwikkeling vertragen, vooral als er geen duidelijke noodzaak is voor deze abstracties.

  3. Steilere leercurve
    Clean Architecture vereist een goed begrip van de principes van lagen en afhankelijkheden. Voor ontwikkelaars die niet gewend zijn aan deze aanpak kan het even duren voordat ze gewend zijn aan de scheiding van verantwoordelijkheden en de juiste plaats voor bepaalde logica.

  4. Potentiële prestatie-impact
    Omdat Clean Architecture vaak gebruik maakt van meerdere abstracties en lagen, kan dit in sommige gevallen leiden tot een prestatie-impact. De afhankelijkheden tussen lagen kunnen ervoor zorgen dat methoden minder efficiënt worden uitgevoerd dan wanneer ze direct in de UI of infrastructuurlaag zouden zitten.

  5. Meer boilerplate code
    Door de nadruk op het scheiden van lagen en het gebruik van abstracties, kan de hoeveelheid boilerplate code toenemen. Dit betekent meer code die beheerd en onderhouden moet worden, wat soms als overbodig kan aanvoelen voor eenvoudiger projecten.


Clean Architecture biedt dus aanzienlijke voordelen op het gebied van onderhoudbaarheid, testbaarheid, en schaalbaarheid, maar komt met extra complexiteit en overhead, vooral voor kleinere applicaties. Het is vooral effectief in grotere, complexere systemen die op lange termijn onderhoud nodig hebben.

Hoe gebruik je het structureren van een project?

De keuze van de juiste projectstructuur moet altijd passen bij de situatie van het project. Er zijn verschillende factoren die meespelen bij het bepalen van de beste aanpak. De grootte van het project, de complexiteit, de samenwerking binnen het team en de verwachte groei van het project spelen allemaal een belangrijke rol in deze beslissing.

Complexiteit

Een van de belangrijkste overwegingen is de complexiteit van het project. Voor een klein project met een beperkte functie kan een simpele mappenstructuur voldoende zijn. Dit maakt het snel en gemakkelijk om te beginnen. Maar bij grotere projecten, vooral die met veel onderdelen, kan het handig zijn om een meer gestructureerde aanpak te kiezen. Dit zorgt ervoor dat de verschillende delen van het project goed georganiseerd blijven en makkelijker te onderhouden zijn.

Structuur

Een andere belangrijke keuze is of je een monolithisch (alles in één project) of gescheiden (meerdere kleinere projecten) structuur wilt gebruiken. In een monolithische structuur worden alle onderdelen van de applicatie samengevoegd, wat in het begin makkelijker te beheren is. Maar naarmate het project groeit, kan het moeilijker worden om alles goed bij te houden. Het splitsen van het project in kleinere delen (bijvoorbeeld microservices) maakt het makkelijker om onderdelen afzonderlijk te werken en te schalen zonder dat het hele project in de war raakt.

Teamsamenstelling

De samenwerking binnen het team is ook belangrijk. Bij kleine teams kan een eenvoudige structuur voldoende zijn, maar naarmate het team groter wordt, is het belangrijk dat de structuur duidelijk is en dat verschillende teamleden makkelijk kunnen samenwerken zonder elkaar in de weg te zitten. Dit betekent dat de structuur ruimte moet bieden voor meerdere mensen die tegelijkertijd aan verschillende onderdelen werken.

Onderhoudbaarheid

Daarnaast is het belangrijk om na te denken over de onderhoudbaarheid van de structuur. De gekozen structuur moet ervoor zorgen dat het project in de toekomst makkelijk te beheren en uit te breiden is. Dit betekent dat de structuur niet te ingewikkeld moet zijn, maar wel flexibel genoeg moet blijven om mee te groeien met het project.

Toekomstige groei

Tot slot moet je bij het kiezen van een projectstructuur rekening houden met de toekomstige groei van het project. Wat nu werkt voor een klein prototype, is misschien niet geschikt als het project in de toekomst veel groter wordt. Het is belangrijk om een structuur te kiezen die ruimte biedt voor uitbreiding zonder dat het project moeilijker te beheren wordt.

Samengevat, het kiezen van de juiste projectstructuur is afhankelijk van verschillende factoren zoals de grootte van het project, de complexiteit, de samenwerking binnen het team, en de toekomstige groei. Door goed na te denken over deze factoren, kun je een structuur kiezen die nu goed werkt en in de toekomst makkelijk aangepast kan worden.

Bronnen

Basics of Clean Architecture with C#: https://medium.com/@wgyxxbf/basics-of-clean-architecture-with-c-80c4df482eac \
Clean Architecture with .NET and .NET Core — Overview: https://medium.com/@wgyxxbf/basics-of-clean-architecture-with-c-80c4df482eac
Clean Architecture in .NET: https://code-maze.com/dotnet-clean-architecture/
Complete Guide to Clean Architecture: https://www.geeksforgeeks.org/complete-guide-to-clean-architecture/ \


Volgende stap: Uitleg structureren code