I hate complicated code! 😠
But do you know what I absolutely despise
from the depths of my cold, black heart?" Needlessly Complicated Code " - That is the worst !! 🤬
An abomination of the highest order, I tell you !!!
So, you can imagine the horror of me seeing my codebase mutate into a quagmire
of interconnected code segments, i.e. files with a needlessly long list of dependencies that would break if I even looked at them the wrong way .It was a mess !!
So, what did I do next? I started researching Software Architecture.
I was looking for something that would produce decoupled code and give the added advantage of helping us migrate to microservices , if the need arose.
Long story short, I decided to go with Clean Architecture
Why you ask? Well, you can read it all about here : Embracing Clean Architecture: A Journey to Better Code
Let's now look into the fundamentals of Clean Architecture before trying to implement it for a simple Notes App
( Why a simple notes app? Cause, understanding clean architecture is hard enough … If we add more complexity, it would end up being incomprehensible for someone getting introduced to clean architecture for the first time )
Clean Architecture stands on the following pillars:
- 1️⃣ Inversion of Control ( Dependency Inversion )
- 2️⃣ Separation of Concerns ( Software Divided into layers - Presentation, Data, Application, Domain )
- 3️⃣ One-way flow of data ( From the outer layer to the inner layer, with the inner layers knowing nothing about the outer layers)
* * You can read about Clean Architecture in detail on : Uncle Bob's Website
I found this to be the simplest way to visualise Clean Architecture
( WHAT'S GREAT ABOUT THIS PICTURE IS THAT YOU CAN DIRECTLY TRANSLATE IT TO CODE )
main.ts
A look at our Project`s Directory Structure :
/src
│── main.ts
│── server.ts
│── presentation
│ └── routers
│ └── notes-router.ts
├── domain
│ ├── interfaces
│ │ ├── repositories
│ │ │ └── notes-repository.ts
│ │ └── use-cases
│ │ └── notes
│ │ ├── create-note-use-case.ts
│ │ ├── delete-note-use-case.ts
│ │ ├── get-all-notes-use-case.ts
│ │ ├── get-one-note-use-case.ts
│ │ └── update-note-use-case.ts
│ ├── models
│ │ └── note.ts
│ ├── repositories
│ │ └── notes-repository.ts
│ └── use-cases
│ └── notes
│ ├── create-note.ts
│ ├── delete-note.ts
│ ├── get-all-notes.ts
│ ├── get-one-note.ts
│ └── update-note.ts
└── data
├── interfaces
│ └── data-sources
│ ├── nosql-database-wrapper.ts
│ └── note-data-source.ts
└── data-sources
├── mongodb
└── mongodb-notes-data-source.ts
Let's start with our first use case test : [ Create Note ]
(Observe how few dependencies we have in our test file )
In each Test File, we are going to test the implementation of a Single Unit of Test ( SUT ), and are going to mock all the related dependencies
So in the case of the create note use case our SUT is the implementation of the create note use case interface ,i.e : the CreateNote class , and we have a single dependency to mock : NotesRepository
When we try to execute this file, we'll encounter failure immediately, as nothing has been implemented as yet!
That's great, and it's by design … as we'll be practising the TDD way of doing things.
The great thing about following Clean Architecture is that it allows you to apply TDD practices from the get-go and in a very convenient way.
The whole system of Clean Architecture promotes separation of concerns and dependency injection, which are great for testing, as mocks can be easily created and injected into a SUT
So, let's get started by defining the CreateNotesUseCase Interface
Now, we observe that we need to define 2 more types to work with the Create Note Use Case Interface.
These 2 types, namely the NoteRequestModel and NoteResponseModel describe the format of the input data that are supplied to the CreateNoteUseCase's execute method and the format of the data we'll get in response to saving it to the Database using the NotesRepository abstraction.
Let's quickly define these 2 types :
Great, now we need to mock our singular dependency for the create note use case : ( NotesRepository )
The NotesReposity interface would provide the methods to CREATE, READ, UPDATE and DELETE notes.
Let's define the NotesRepository interface before mocking it :
Finally , it's time for us to create the NotesRepository mock , and a helper function called getMockNotesRepository
Another helper required to run our test :
Now, we need to define our SUT ( the concrete implementation of the CreateNoteUseCase )
With all the setup done, we can finally go ahead and run our test!
And, we get a passing test :
What's next? Well as an assignment, go ahead and try to implement the rest of the use cases ( DeleteNoteUseCase, UpdateNoteUseCase, GetNotesUseCase, GetOneNoteUseCase ).
Do follow the same TDD methodology while implementing your tests
We'll meet again and test the NotesRepository, till then … Adios !!
You can view the complete code and the repository with all test cases implemented over here: GutHub Repo
My blog is inspired by a number of other blogs on the same subject, which are as follows :
1 . Clean Architecture Contacts
2 . Clean Architecture Fundamentals
3.TDD Fundamentals
Top comments (0)