Skip to content

Commit

Permalink
docs: update READMEs
Browse files Browse the repository at this point in the history
  • Loading branch information
adbayb committed Dec 26, 2024
1 parent edd392a commit 87b7b5d
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 71 deletions.
56 changes: 46 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,67 @@
<br>
<div align="center">
<h1>📦 clean-architecture</h1>
<strong>A clean architecture example to enforce a loosely coupled business logic</strong>
<strong>A clean architecture implementation example to enable testable and evolutive systems</strong>
</div>
<br>
<br>

## ✨ Features

TODO
A TypeScript implementation of [the Clean Architecture specified by Robert C. Martin (Uncle Bob)](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html).

<br>
The Clean Architecture is an architectural pattern (a layered architecture) that enables the creation of loosely coupled and testable systems by enforcing a clear separation of concerns. This separation isolates the business rules from the details (such as frameworks and external dependencies like databases), making the system more maintainable and evolutive.

## 🚀 Usage
Drawing inspiration from [the Vertical Slice Architecture](https://www.jimmybogard.com/vertical-slice-architecture/) (which emphasizes organizing code around vertical slices of business functionality), a **modular architecture** has been also implemented to encapsulate and group all concerns from presentation to data per bounded context[^1].
This modular approach enables self-contained systems[^2], minimizes coupling between slices/maximizes cohesion in a slice, and helps to foster a [screaming architecture](https://blog.cleancoder.com/uncle-bob/2011/09/30/Screaming-Architecture.html).

This section introduces the `clean-architecture` essentials by walking through its main commands:
[^1]: In this repository, a [bounded context](https://martinfowler.com/bliki/BoundedContext.html) is implemented by one module, so a bounded context is equivalent to a module here. However, it's not always the case since [a bounded context is not strictly equivalent to a module](https://stackoverflow.com/a/77923055). Indeed, while a module is a technical-oriented concept that defines logical boundaries in the code, a [bounded context](https://deviq.com/domain-driven-design/bounded-context) is a business-oriented one (domain-driven design tactical pattern) that represents a [cohesive area of the business domain](https://ddd-practitioners.com/2023/03/07/the-difference-between-domains-subdomains-and-bounded-contexts/). A module is a technical enabler to implement a bounded context, which can contain one or multiple modules.

0️⃣ ...
1️⃣ ...
2️⃣ ...
3️⃣ ...
[^2]: A slice can decide in autonomy which architecture makes the most sense depending on the business nature and complexity. For example, a module A with no or little business logic can implement a non-layered architecture (e.g. MVC, [transaction scripts](https://martinfowler.com/eaaCatalog/transactionScript.html), ...) while a module B with more extensive and complex business rules can use a [layered-like architecture to allow better separation of concerns](https://ddd-practitioners.com/home/glossary/layered-architecture/) (e.g. Clean Architecture, Onion Architecture, ...). In the repository, we're focusing on the clean architecture. Consequently, each module implements this architecture.

<br>

## 🏗️ Architecture

TODO
### Overview

**TODO (architecture diagram with control flow following [Clean architecture diagram (from the book)](https://i.sstatic.net/K44FQ.jpg)).**

### Components

Used building blocks (including [DDD tactical patterns](https://vaadin.com/blog/ddd-part-2-tactical-domain-driven-design)):

- Entities
- Value Objects
- ...

### Layers

![Clean Architecture Layers](https://blog.cleancoder.com/uncle-bob/images/2012-08-13-the-clean-architecture/CleanArchitecture.jpg)

- [Enterprise Business Rules (Entities)](./modules/catalog/src/entities/): TODO.
- [Application Business Rules (Use Cases)](./modules/catalog/src/useCases/): TODO.
- [Interface Adapters](./modules/catalog/src/adapters/): Concrete implementation of ports folder that can include entity gateways, interface for data source implemented framework side, other repository/service gateways and GUI design pattern implementation (MVC, MVVM, MVP, ...).
- [Frameworks & Drivers](./modules/catalog/src/frameworks/): React views (and hooks), data source (including HttpDataSource to make fetch calls with error management, database client (Redis, SQL, MongoDB, ...), ...), ... TODO (include hosts (main component orchestrator)).
- [Hosts](./hosts): Act like the configurator instance in the [Hexagonal Architecture](https://alistaircockburn.com/Hexagonal%20Budapest%2023-05-18.pdf). Under the Clean Architecture, the host layer is the outermost layer. It includes the initial entry point of the system called the main component in the Clean Architecture book (in the "Main Component" chapter). This layer is not depicted in the diagram shown above. The main component is on the driver side (for example, Web UI, CLI, Back-end server, ...) and is responsible to instantiate inner layers.

### Modules

**TODO: package diagram / choosen feature split (catalog, ...) with slice across the different layers.**

#### A special module: the Shared Kernel.

See [documentation](./modules/shared-kernel/).

<br>

## 📚 Resources

- [Clean Architecture original blog post](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html).
- [Clean Architecture book summary](https://github.com/serodriguez68/clean-architecture/).
- [Domain-Driven Design tactical design and Clean Architecture (video)](https://www.youtube.com/watch?v=hf_XBb5cSoA).
- [Pull-based vs push-based approach managing use case output](https://softwareengineering.stackexchange.com/a/420360) or [why presenters (push-based) should be preferred to returned use case values (pull-based)](https://lukemorton.tech/articles/nuances-in-clean-architecture). TLDR; Communication from Controller to Presenter is meant to go through the application layer, making the Controller do part of the Presenters job is likely a domain/application leak.
- [Single responsibility principle and mixing presenter/controller](https://stackoverflow.com/questions/64415618/clean-architecture-controller-and-presenter-should-always-be-separate-classes-o). TLDR; Not mixing them allows better flexibility later if some other ways of displaying data (i.e. presenters) should be supported (Web, CLI, JSON, ...).

<br>

Expand Down
2 changes: 0 additions & 2 deletions hosts/README.md

This file was deleted.

57 changes: 7 additions & 50 deletions hosts/web/README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,7 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:

- Configure the top-level `parserOptions` property like this:

```js
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
tsconfigRootDir: import.meta.dirname,
},
},
});
```

- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:

```js
// eslint.config.js
import react from "eslint-plugin-react";

export default tseslint.config({
// Set the react version
settings: { react: { version: "18.3" } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs["jsx-runtime"].rules,
},
});
```
<br>
<div align="center">
<h1>📦 Clean Architecture</h1>
<strong>The web host</strong>
</div>
<br>
<br>
4 changes: 1 addition & 3 deletions modules/catalog/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
<br>
<div align="center">
<h1>📦 Clean Architecture</h1>
<strong>Catelog module.</strong>
<strong>The catalog [bounded context](https://deviq.com/domain-driven-design/bounded-context)</strong>
</div>
<br>
<br>

TODO (architecture diagram with control flow following [Clean architecture diagram (from the book)](https://i.sstatic.net/K44FQ.jpg)).
3 changes: 0 additions & 3 deletions modules/catalog/src/adapters/README.md

This file was deleted.

1 change: 0 additions & 1 deletion modules/catalog/src/frameworks/README.md

This file was deleted.

3 changes: 2 additions & 1 deletion modules/catalog/src/frameworks/web/GetQuoteView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { GetQuoteViewModel } from "../../adapters/GetQuoteViewModel";
import { GetQuotePresenter } from "../../adapters/GetQuotePresenter";
import { GetQuoteController } from "../../adapters/GetQuoteController";
import { useDependencyInjection } from "./useDependencyInjection";
import type { Hook } from "./types";

export const GetQuoteView = () => {
const { controller, viewModel } = useGetQuote();
Expand All @@ -28,7 +29,7 @@ export const GetQuoteView = () => {
return null;
};

const useGetQuote = () => {
const useGetQuote: Hook<GetQuoteController, GetQuoteViewModel> = () => {
const { quoteEntityGateway } = useDependencyInjection();
const [viewModel, setViewModel] = useState<GetQuoteViewModel>({});
const presenter = useMemo(() => new GetQuotePresenter(setViewModel), []);
Expand Down
6 changes: 6 additions & 0 deletions modules/catalog/src/frameworks/web/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { Controller, ViewModel } from "@clean-architecture/shared-kernel";

export type Hook<C extends Controller, VM extends ViewModel> = () => {
controller: C;
viewModel: VM;
};
2 changes: 1 addition & 1 deletion modules/shared-kernel/src/Controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { UseCaseInputPort } from "./UseCase";
import type { RequestModel } from "./RequestModel";

export abstract class Controller<Model extends RequestModel> {
export abstract class Controller<Model extends RequestModel = RequestModel> {
// Controller must have no reference to presenter to prevent coupling between both different object and allow more easier interchangeability (using another presenter with the same controller context for example)
public constructor(private readonly useCase: UseCaseInputPort<Model>) {}

Expand Down

0 comments on commit 87b7b5d

Please sign in to comment.