This repository is a real life example of Clean Architecture with use of Next.js
and Typescript
Diagrams have copyrights, if you want to use it on larger scale, feel free to contact me.
Project is a simple logistic dashboard for managing packages and clients.
There are two separate roles Admin
and Member
.
TODO
Given I'm Admin
When I'm on Dashboard
Then I see recent new packages
And I see recent packages in transit
And I see packages delivered
Given I'm Admin
When I'm on clients screen
Then I can see clients list
Given I'm Admin
When I'm on clients screen
And I click on client item
Then I'm navigated to client details screen
Given I'm Admin
When I'm on client details screen
Then I can see client name
And packages list
Given I'm Admin
When I'm on packages screen
Then I can see packages list
Given I'm Admin
When I'm on packages screen
And I click on package item
Then I can see package products
Given I'm Admin
When I tap on logout button
Then I'm navigated to login screen
Yarn
Docker
Typescript
Next.js 14
Docker
Cucumber
Mockoon
Jest
Playwright
Storybook
Yarn install
-
app (
Next.js app folder
)Contains application structure related to Next.js 14
-
core (
Application Core
)Contains loggers, event tracking logic etc
-
data
Contains definition of data sources
-
dataStore
Contains definition of state like redux, zustand etc
-
domain
Contains definition of models, repositories, usecases etc
-
ioc
Contains definition of dependency injection related structures ( Container and modules )
-
presentation
Contains definition of presenters ( MVP ) and presenter related models
-
ui
Contains definition for user facing markup ( React.js ), server side components, styles etc
There is specific data flow applied for Clean architecture, and it's important to understand layers separation. In this example you can see clean architecture with MVP ( Model View Presenter ) pattern as it seems like the best solution long term when it comes to web apps development. There are few alternatives which you can see on diagram. Code is focused on primary ( first proposal ).
Whole approach defines layer access restrictions which are defined in eslint
.
DEPENDENCY INJECTION
has access to every layer to provide proper abstractions implementations.UI
have access toPRESENTATION ( MVP )
andDATA STORE
layersProduct features
can be grouped intoBounded Context
- Given
Product features
do not have direct access to each other,- If you need to have some cross product functionality then you need to define
shared
product feature.
- If you need to have some cross product functionality then you need to define
PRESENTATION
have access toDATA STORE
andDOMAIN
Presenters
can be also part ofBounded Context
and also can be part ofshared
product featurePresenters
can access data store in need to update data ( when fetching data from data sources ) or fetch it
DATA STORE
can't access any layer, it defines data structures and stores data mostly related to some frameworkDOMAIN LAYER
can't access any layer, it's the core of whole architecture. It defines structures and business logicMappers
are defined here and are responsible for mapping data source models into domain models in scope of repositoryUse Cases
,Scenarios
,Interactors
defines business logic and communicates with repositoriesRepositories
in domain are just abstractions / interfaces, not a specific implementation
Take a look at detailed diagrams for every layer for better understanding data flow and related structures
docker compose up
- To access app
http://localhost:3000/
- To access storybook
http://localhost:6006/
Every role has its own account
You can use any password
- Run
yarn test
- Run
yarn test:e2e
- Read guide here to setup global dependencies
- Run
yarn test:mutate
command
Page Object
- models objects used within specific tests.
Request object
- holds data which is transferred between modules ( DTO
).
Repository
is an abstraction over data source. Defined actions which can be done over data source, and clear definition of
input and output
Unit of work
is a wrapper around multiple repositories, you can use it if you have dependency between few data sources,
and you would like to combine output into one model
UseCase
can be understood as operation performed by user which is based on a specific data source.
The Simple concept, where one module data structure is translated to another module
This mapper is prepared for mapping data source format data into domain format. The Simplest example would be that, in
a database we store first_name
and last_name
in separate columns, but in a domain we need to have field name
which
is combined value of previously mentioned columns. In that case we define domain model with required fields and new name
field.
In a Mapper, we can perform merging of those 2 values.
This mapper is for preparing Domain data format into specific presentation data format. This mapper separates domain data format
from UI / Presentation data format. If you plan to use dataStore
layer then you can combine domain
+ dataStore
data
into one presentation
model for specific screen
It's a simple pattern for better access control within domains and folders. Sometimes we want to just keep some files accessible within given package. To achieve that we are using scopes.
- Order of stylesheets combined with routing - GitHub Issue here
- After update to
14.2.0-canary.60
canary version seems like styling issue related to order has been resolved, but - there is different issue now - sometimes styles are not loaded for some components and user needs to refresh
- After update to
- Slow routes loading vercel/next.js#61259
- There are still some weird issues over authentication...
- Project is sometimes still unstable, it's crashing here and there. I'll try to correct it in a free moment
- Applied some patches to gracefully handle crashes due to lack of mocked data
- Add more tests for prepared code ( e2e and unit test )
- Update exising tests
- Add more functionality to existing project