From e4bb01e9f3e21a9f87f4cee7abb424f21dc9aab8 Mon Sep 17 00:00:00 2001 From: Ayoub Adib Date: Thu, 26 Dec 2024 02:47:33 +0100 Subject: [PATCH] docs: update READMEs --- README.md | 56 ++++++++++++++---- hosts/README.md | 2 - hosts/web/README.md | 57 +++---------------- modules/catalog/README.md | 4 +- modules/catalog/src/adapters/README.md | 3 - modules/catalog/src/frameworks/README.md | 1 - .../src/frameworks/web/GetQuoteView.tsx | 3 +- modules/catalog/src/frameworks/web/types.ts | 6 ++ modules/shared-kernel/src/Controller.ts | 2 +- 9 files changed, 63 insertions(+), 71 deletions(-) delete mode 100644 hosts/README.md delete mode 100644 modules/catalog/src/adapters/README.md delete mode 100644 modules/catalog/src/frameworks/README.md create mode 100644 modules/catalog/src/frameworks/web/types.ts diff --git a/README.md b/README.md index 24555f5..da5a147 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,67 @@

📦 clean-architecture

- A clean architecture example to enforce a loosely coupled business logic + A clean architecture implementation example to enable testable and evolutive systems


## ✨ 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). + +The clean architecture enables loosely coupled testable systems by enforcing separation of concerns and making frameworks and more generally external dependencies (database, ...) a detail. + +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](https://martinfowler.com/bliki/BoundedContext.html)[^1]. +This modular approach allows minimizing coupling between slices, and maximizing cohesion in a slice. It enables also expressiveness structure by fostering a [screaming architecture](https://blog.cleancoder.com/uncle-bob/2011/09/30/Screaming-Architecture.html). +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. + +[^1] In this repository, a bounded context 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.
-## 🚀 Usage +## 🏗️ Architecture + +### 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)): -This section introduces the `clean-architecture` essentials by walking through its main commands: +- Entities +- Value Objects +- ... -0️⃣ ... -1️⃣ ... -2️⃣ ... -3️⃣ ... +### 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/).
-## 🏗️ Architecture +## 📚 Resources -TODO +- [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, ...).
diff --git a/hosts/README.md b/hosts/README.md deleted file mode 100644 index 49c8207..0000000 --- a/hosts/README.md +++ /dev/null @@ -1,2 +0,0 @@ -Act like configurator instances in [hexagonal architecture](https://alistaircockburn.com/Hexagonal%20Budapest%2023-05-18.pdf). -Each host is on the driver side (for example, Web UI, CLI, Back-end server, ...) and responsible to instantiate the hexagon. diff --git a/hosts/web/README.md b/hosts/web/README.md index 66d9eda..91d6400 100644 --- a/hosts/web/README.md +++ b/hosts/web/README.md @@ -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, - }, -}); -``` +
+
+

📦 Clean Architecture

+ The web host +
+
+
diff --git a/modules/catalog/README.md b/modules/catalog/README.md index 5c3a4cc..5c0cf87 100644 --- a/modules/catalog/README.md +++ b/modules/catalog/README.md @@ -1,9 +1,7 @@

📦 Clean Architecture

- Catelog module. + The catalog [bounded context](https://deviq.com/domain-driven-design/bounded-context)


- -TODO (architecture diagram with control flow following [Clean architecture diagram (from the book)](https://i.sstatic.net/K44FQ.jpg)). diff --git a/modules/catalog/src/adapters/README.md b/modules/catalog/src/adapters/README.md deleted file mode 100644 index 6a244e4..0000000 --- a/modules/catalog/src/adapters/README.md +++ /dev/null @@ -1,3 +0,0 @@ -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, ...). - -https://proandroiddev.com/the-real-repository-pattern-in-android-efba8662b754 diff --git a/modules/catalog/src/frameworks/README.md b/modules/catalog/src/frameworks/README.md deleted file mode 100644 index 09c6181..0000000 --- a/modules/catalog/src/frameworks/README.md +++ /dev/null @@ -1 +0,0 @@ -Framework/driver-related folder that can include React views (and hooks), data source (including HttpDataSource to make fetch calls with error management, database client (Redis, SQL, MongoDB, ...), ...), ... diff --git a/modules/catalog/src/frameworks/web/GetQuoteView.tsx b/modules/catalog/src/frameworks/web/GetQuoteView.tsx index a5ac349..0790968 100644 --- a/modules/catalog/src/frameworks/web/GetQuoteView.tsx +++ b/modules/catalog/src/frameworks/web/GetQuoteView.tsx @@ -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(); @@ -28,7 +29,7 @@ export const GetQuoteView = () => { return null; }; -const useGetQuote = () => { +const useGetQuote: Hook = () => { const { quoteEntityGateway } = useDependencyInjection(); const [viewModel, setViewModel] = useState({}); const presenter = useMemo(() => new GetQuotePresenter(setViewModel), []); diff --git a/modules/catalog/src/frameworks/web/types.ts b/modules/catalog/src/frameworks/web/types.ts new file mode 100644 index 0000000..dd6cca5 --- /dev/null +++ b/modules/catalog/src/frameworks/web/types.ts @@ -0,0 +1,6 @@ +import type { Controller, ViewModel } from "@clean-architecture/shared-kernel"; + +export type Hook = () => { + controller: C; + viewModel: VM; +}; diff --git a/modules/shared-kernel/src/Controller.ts b/modules/shared-kernel/src/Controller.ts index 75042f1..b3b00f7 100644 --- a/modules/shared-kernel/src/Controller.ts +++ b/modules/shared-kernel/src/Controller.ts @@ -1,7 +1,7 @@ import type { UseCaseInputPort } from "./UseCase"; import type { RequestModel } from "./RequestModel"; -export abstract class Controller { +export abstract class Controller { // 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) {}