/clean-architecture

Examples on how to use Clean Architecture & TDD in applications using TypeScript.

Primary LanguageTypeScript

Clean Architecture and Test Driven Development (TDD) using TypeScript

Clean Architecture Banner

Clean Architecture is an architecture design developed by Robert C. Martin (Uncle Bob). You will find many tutorials talking about the theory and concepts behind Clean Architecture. How you choose to apply these concepts in your project is up to you. You will see in this article, some variations of this design.

With all these concepts, I wanted to provide a real-world example using best practices applying Clean Architecture.

Clean Architecture Diagram

What does this example project do?

The application fetches number trivia from Numbers API when connected to the internet (online). If the application detects a network connection is no longer present (offline), it will fetch the last trivia from local cache. You can fetch trivia from a number you provide or a random number provided by the API. Because the business logic independent of any presentation or frameworks, applications representing the presentation layer can be built using different UI framework and adapters. This project provides some concrete implementations using React.js and Vue.js.

Application Example

What's inside?

This project was built using Turborepo. This turborepo uses pnpm as a package manager. It includes the following packages/apps:

Apps and Packages

  • apps/react: a React.js app
  • apps/vue: a Vue.js app
  • apps/server: a Node.js web service. See Readme
  • packages/business: business logic demonstrating how to implement the domain and data layers of clean architecture.
  • packages/eslint-config-custom: eslint configurations (includes eslint-config-next and eslint-config-prettier)
  • packages/tsconfig: tsconfig.jsons used throughout the monorepo

Each package/app is 100% TypeScript.

Presentation layer

Web apps

To demonstrate a clean presentation layer, the web applications use Shoelace, a web component library built on Lit. Developing web components is a best practice when you want to apply the DRY (Don't repeat yourself) principle.

Server

Custom adapters were developed that leverage our business logic but meet the requirements needed by the server. See Readme

Test Driven Development (TDD)

The demo is built using TDD ensuring all business logic passes and has coverage. Tests were built and tested using Vitest. Vitest is built on top of Jest. If you know Jest, you know Vitest. Vitest supports TypeScript by default. Here is a great overview video on Vitest: https://www.youtube.com/watch?v=7f-71kYhK00

Code Coverage

Getting Started

Installing dependencies

To install all apps and packages dependencies, run the following command from the project root:

Build

To build all apps and packages, run the following command:

pnpm run build
pnpm install

Test

To test all apps and packages, run the following command:

pnpm run test

The test will be reported in the terminal and in HTML packages/business/coverage/index.html.

Develop

To develop all apps and packages, run the following command:

pnpm run dev

Utilities

This turborepo has some additional tools already setup for you:

Tricks to discovered along the way...

  1. In order to get the business package to work with the server and web applications, storage needed to be async. An important note when building things is its better to go async route initially to avoid reworking code later.

  2. Getting the dev and build environments to play with each other was a real pain. The web apps use vite while the server uses just the TypeScript compiler. Neither the vite nor the TS docs helped with this issue. I did read thoroughly this section in the Vite docs - https://vitejs.dev/guide/build.html#building-for-production. But what it really came down to was that I had some luck with the compiling by placing this in the vite.config.ts file:

optimizeDeps: {
  include: [
    "business",
  ],
},
build: {
  rollupOptions: {}
},

Notice that the build.rollupOptions just contains an empty object. I don't know why but this works along with the optimizeDeps. I don't like that there are some unknowns there at the moment but the project will run and build all projects correctly.

Roadmap

  • Provide a application example in React.
  • Provide a application example in Vue.
  • Provide a service example using Node.js.
  • Provide a command line example that can be run from the terminal.
  • Provide a desktop example using Electron.

Credits

This project is inspired by Flutter tutorials by Matt Rešetár

License

MIT license.