Skip to content

Commit f3b98a0

Browse files
committed
Fixed typos and semantic errors in the DOMAIN_RESOURCE_ACTION_ARCHITECTURE.md.
Signed-off-by: Exadra37 <exadra37@gmail.com>
1 parent 8746c8c commit f3b98a0

File tree

1 file changed

+35
-35
lines changed

1 file changed

+35
-35
lines changed

elixir/phoenix/DOMAIN_RESOURCE_ACTION_ARCHITECTURE.md

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# Domain Resource Action Architecture
22

3-
The Domain Resource Action is an architecture pattern where each module for the Business Logic layer represents a single action possible on a Resource of a Domain or SubDomain of a Domain. This means that each module for a Resource Action is responsible for only one action, therefore it can only contain one public method, and when necessary it's bang version, the function name ending with `!`, e.g. `read/1` and `read!/1`.
3+
The Domain Resource Action is an architecture pattern where each module for the Business Logic layer represents a single action possible on a Resource of a Domain or SubDomain of a Domain. This means that each module for a Resource Action is responsible for only one action, therefore it can only contain one public method, and when necessary its bang version, the function name ending with `!`, e.g. `read/1` and `read!/1`.
44

5-
All modules inside a Resource Action can only be accessed through the Resource API module from the web layer (LiveViews, Controllers, etc.), from other Domain Resources or from the same Resource. This reduces accidental complexity by avoiding direct coupling between modules across boundaries. This also makes very easy to refactor later the code because anything consuming the Business Logic is only aware of the API module for the Resource.
5+
All modules inside a Resource Action can only be accessed through the Resource API module from the web layer (LiveViews, Controllers, etc.), from other Domain Resources or from the same Resource. This reduces accidental complexity by avoiding direct coupling between modules across boundaries. This also makes it very easy to refactor later the code because anything consuming the Business Logic is only aware of the API module for the Resource.
66

7-
Each Resource Action is unit tested and the Resource API it's only tested via its doc-tests to ensure docs examples are in sync with the code and that each function can be invoked.
7+
Each Resource Action is unit tested and the Resource API is only tested via its doc-tests to ensure docs examples are in sync with the code and that each function can be invoked.
88

9-
Any project following this DOMAIN_RESOURCE_ACTION_ARCHITECTURE.md MUST strictly adhere to [1. Folder Structure](#1-folder-structure) and implement [2. Milliseconds Timestamps](#2-milliseconds-timestamps)] and [3. Binary IDs](#3-binary-ids) without making assumptions, in doubt always ask to the user for clarifications.
9+
Any project following this DOMAIN_RESOURCE_ACTION_ARCHITECTURE.md MUST strictly adhere to [1. Folder Structure](#1-folder-structure) and implement [2. Milliseconds Timestamps](#2-milliseconds-timestamps) and [3. Binary IDs](#3-binary-ids) without making assumptions, when in doubt always ask the user for clarifications.
1010

1111
## 1. Folder Structure
1212

@@ -86,7 +86,7 @@ lib
8686
│ │ │ ├── change
8787
│ │ │ │ └── change_warehouse_stock.ex
8888
│ │ │ └── stock.ex
89-
│ │ └── wharehouses_stocks_api.ex
89+
│ │ └── warehouses_stocks_api.ex
9090
│ └── shared_domains_api.ex
9191
├── my_app_web
9292
│ ├── live
@@ -115,10 +115,10 @@ Breaking down the partial folder structure example for an Online Shop:
115115
- Resources: `categories`, `products`, `stocks`
116116
- Shared Folders: `shared_resources`, `shared_domains`
117117
- Actions: `subscribe`, `broadcast`, `list`, `get`, `create`, `update`, `delete`, `change`, `bulk_create`, `bulk_update`, `bulk_delete`
118-
- Resource APIs: `catalogs_categories_api.ex`, `catalogs_products_api.ex`, `wharehouses_stocks_api.ex`, `shared_resources_api.ex`, `shared_domains_api.ex`
118+
- Resource APIs: `catalogs_categories_api.ex`, `catalogs_products_api.ex`, `warehouses_stocks_api.ex`, `shared_resources_api.ex`, `shared_domains_api.ex`
119119
- Ecto Schemas: `category.ex`, `product.ex`, `stock.ex`
120120

121-
NOTE: Both Domains and Resources may have a shared folder for actions that are shared or for other things that common to them. Shared folders don't have by default a schema, but they **MUST** have always the API module.
121+
NOTE: Both Domains and Resources may have a shared folder for actions that are shared or for other things that are common to them. Shared folders don't have a schema by default, but they **MUST** always have the API module.
122122

123123
### 1.2 Patterns to Create Files and Directories
124124

@@ -127,7 +127,7 @@ NOTE: Both Domains and Resources may have a shared folder for actions that are s
127127
As per the folder structure:
128128
- Directory: `lib/my_app/<domain_plural>/`
129129
- File: `<domain_plural>_<resource_plural>_api.ex` (e.g., `lib/my_app/catalogs/catalogs_products_api.ex`)
130-
- Module: `MyApp.<DomainPlural>.<DomainPlural><ResourcePlural>API` (e.g., `MyApp.Catalogs.CatalogsProductsAPI`). Catalogs is the Domain, Categories the Resource, and API the Type of module.
130+
- Module: `MyApp.<DomainPlural>.<DomainPlural><ResourcePlural>API` (e.g., `MyApp.Catalogs.CatalogsProductsAPI`). Catalogs is the Domain, Products the Resource, and API the Type of module.
131131

132132
#### 1.2.2 Domain Resource Schema
133133

@@ -153,25 +153,25 @@ The Domain folder is located at `lib/my_app/<domain_plural>`, e.g. `lib/my_app/c
153153

154154
Module types:
155155

156-
- `API` - This files are the only way that Resources can be accessed, inclusive from inside the Resource itself. They define the public contract for each Resource on the Domain folder.
156+
- `API` - These files are the only way that Resources can be accessed, including from inside the Resource itself. They define the public contract for each Resource on the Domain folder.
157157

158158
#### 1.3.2 Domain Resource Folder
159159

160160
The Domain Resource folder is located at `lib/my_app/<domain_plural>/<resource_plural>`, e.g. `lib/my_app/catalogs/products`:
161161

162162
Module types:
163163

164-
- `Ecto Schema` - This are the usual Ecto Schema module generated by the Phoenix code generators, e.g, `product.ex`.
164+
- `Ecto Schema` - These are the usual Ecto Schema module generated by the Phoenix code generators, e.g, `product.ex`.
165165

166166

167167
#### 1.3.3 Domain Resource Action Folder
168168

169-
The Domain Resource folder is located at `lib/my_app/<domain_plural>/<resource_plural>/<action>`, e.g. `lib/my_app/catalogs/products/update`:
169+
The Domain Resource Action folder is located at
170170

171171
Module Types:
172172

173-
- For simple actions, like the ones generate by Phoenix code generators, a single module will suffice. For example: `update_category_product.ex`.
174-
- For complex actions it may be wise to separate in at least into three modules:
173+
- For simple actions, like the ones generated by Phoenix code generators, a single module will suffice. For example: `update_category_product.ex`.
174+
- For complex actions it may be wise to separate at least into three modules:
175175
- `Handler` - entrypoint module to coordinate the work being done.
176176
- `Core` - for pure business logic without side effects.
177177
- `Storage`, `Queues`, etc, - a dedicated module per type of communication with the external world.
@@ -184,15 +184,15 @@ Module Types:
184184
Example for the file `catalogs_products_api.ex` in the folder structure example.
185185

186186
```elixir
187-
# lib/my_app/catalog/catalogs_products_api.ex
187+
# lib/my_app/catalogs/catalogs_products_api.ex
188188
defmodule MyApp.Catalogs.CatalogProductApi do
189189
@moduledoc """
190190
The Product API for the Catalogs.
191191
"""
192192

193-
alias MyApp.Catalogs.Products.Update.UpdateCatalogProductHandler
193+
alias MyApp.Catalogs.Products.Update.UpdateCatalogProduct
194194

195-
# Not using defdelegate because we will loose the API contract
195+
# Not using defdelegate because we will lose the API contract
196196
@doc """
197197
Updates a Product in the Catalogs.
198198
@@ -214,7 +214,7 @@ end
214214

215215
#### 1.4.3 Domain Resource Simple Action
216216

217-
For simple actions, like the ones generate by Phoenix code generators, a single module will suffice. For example: `update_category_product.ex`:
217+
For simple actions, like the ones generated by Phoenix code generators, a single module will suffice. For example: `update_category_product.ex`:
218218

219219

220220
```elixir
@@ -277,7 +277,7 @@ defmodule MyApp.Catalogs.Products.Update.UpdateCatalogProductHandler do
277277
# An alternative is to trigger the reactions to this update with cross boundary calls via the Domain Resource API.
278278
# Using a cross boundary call to the Domain Resource API avoids direct coupling to the internal of the Domain.
279279
# This is soft coupling, which reduces accidental coupling and complexity.
280-
MailerNotifierAPI.notify_users(scope, {:catalog_product_updated, catalogs_product}) # <-- MIDDLE GROUNG RECOMMENDATION
280+
MailerNotifierAPI.notify_users(scope, {:catalog_product_updated, catalogs_product}) # <-- MIDDLE GROUND RECOMMENDATION
281281

282282
# Bear in mind that by doing this approach of direct cross boundary calls we are coupling this module with anything we interact with.
283283
# This is often called accidental complexity via accidental coupling.
@@ -290,13 +290,13 @@ defmodule MyApp.Catalogs.Products.Update.UpdateCatalogProductHandler do
290290
end
291291
```
292292

293-
Calls to core modules from a Domain Resource Action Handler aren't limited to one. This means we can have more than one Core module per action for whatever we need to do in therms of:
293+
Calls to core modules from a Domain Resource Action Handler aren't limited to one. This means we can have more than one Core module per action for whatever we need to do in terms of:
294294
- business rules validation
295295
- data transformation
296296
- data enrichment
297-
- anything else that as no side effects or communicates with the external world.
297+
- anything else that has no side effects or communicates with the external world.
298298

299-
For example, the width clause on the above example would have some more calls to core modules:
299+
For example, the with clause on the above example would have some more calls to core modules:
300300

301301
```elixir
302302
with {:ok, _scope} <- CatalogsProductsAPI.allowed(scope, :update_catalog_product),
@@ -308,7 +308,7 @@ with {:ok, _scope} <- CatalogsProductsAPI.allowed(scope, :update_catalog_product
308308
end
309309
```
310310

311-
This split in several Core modules is useful in complex Business Domains, that have complex rules and data transformations/enrichment's and whatever else. When the Business Domain is straightforward and simple, then we may not even use a Core module if it doesn't make sense for the current Resource Action being handled.
311+
This split in several Core modules is useful in complex Business Domains, that have complex rules and data transformations/enrichments and whatever else. When the Business Domain is straightforward and simple, then we may not even use a Core module if it doesn't make sense for the current Resource Action being handled.
312312

313313
##### 1.4.4.2 Domain Resource Action Core Module Example
314314

@@ -366,7 +366,7 @@ This approach to create the Phoenix commands is **MANDATORY** to generate code f
366366

367367
#### 1.5.2 Fixing Routes with Domain and Resource
368368

369-
Every-time a code generator is used, that supports the option `--web`, it must be used in the format `-web <DomainPlural>.<ResourcePlural>`, e.g. `--web Accounts.Users`.
369+
Every time a code generator is used, that supports the option `--web`, it must be used in the format `-web <DomainPlural>.<ResourcePlural>`, e.g. `--web Accounts.Users`.
370370

371371
Unfortunately a small bug exists in the code generators and the routes will have the resource `users` duplicated, e.g. `http://example.com/accounts/users/users/register`, but instead it needs to be `http://example.com/accounts/users/register`.
372372

@@ -396,25 +396,25 @@ You **MUST** follow this steps:
396396

397397
1. Rename `lib/my_app/catalogs/products.ex` to `lib/my_app/catalogs/catalogs_products_api.ex`
398398
2. Update the module definition from `MyApp.Catalogs.Products` to `MyApp.Catalogs.CatalogsProductsAPI`.
399-
3. Extract each function body from the new module `MyApp.Catalogs.CatalogsProductsAPI` into it's own module with only one public function, named after the action, without the resource name, at `lib/my_app/catalogs/products/<action>/<action>_catalog_product.ex`. For example: `lib/my_app/catalogs/products/create/create_catalog_product.ex` with a function named `create`. The `CatalogsProductsAPI` function header is kept, but its body its now only calling the new action module function, but without using `defdelegate`, otherwise we loose the API contract. You **MUST** also extract private functions like for the `broadcast` action and make them public. Any access from a module to a Domain Resource Action module needs to got through the API module, direct access is **FORBIDDEN**.
399+
3. Extract each function body from the new module `MyApp.Catalogs.CatalogsProductsAPI` into its own module with only one public function, named after the action, without the resource name, at `lib/my_app/catalogs/products/<action>/<action>_catalog_product.ex`. For example: `lib/my_app/catalogs/products/create/create_catalog_product.ex` with a function named `create`. The `CatalogsProductsAPI` function header is kept, but its body is now only calling the new action module function, but without using `defdelegate`, otherwise we lose the API contract. You **MUST** also extract private functions like for the `broadcast` action and make them public. Any access from a module to a Domain Resource Action module needs to go through the API module, direct access is **FORBIDDEN**.
400400
4. Update the tests for the now refactored `MyApp.Catalogs.Products` context to test instead `MyApp.Catalogs.CatalogsProductsAPI`. Rename the test file, Module name, and then replace each call to the context with a call to new API module.
401-
5. Run `mix test` to ensure no test is broken after the refactor. If any test its broken fix it before proceeding.
401+
5. Run `mix test` to ensure no test is broken after the refactor. If any test is broken fix it before proceeding.
402402

403403
#### 1.5.4 Accessing the Business Logic Layer from the Web Layer
404404

405-
Calls from the web layer, like from a live view or controller are only allowed to a Domain Resource API, that in the folder structure example would be to one of the modules defined at `catalogs_categories_api.ex`, `catalogs_products_api.ex` and `wharehouse_stocks_api.ex`. This means that the usual calls to the context need to be replaced with calls to the Domain Resource API. For example, replacing `Catalogs.update_product` with `CatalogsProductsAPI.update_product`. The same needs to be done in the respective tests.
405+
Calls from the web layer, like from a live view or controller are only allowed to a Domain Resource API, that in the folder structure example would be to one of the modules defined at `catalogs_categories_api.ex`, `catalogs_products_api.ex` and `warehouse_stocks_api.ex`. This means that the usual calls to the context need to be replaced with calls to the Domain Resource API. For example, replacing `Catalogs.update_product` with `CatalogsProductsAPI.update_product`. The same needs to be done in the respective tests.
406406

407407
Both a live view and a controller must only have logic to deal with web layer concerns, which usually consists in calling a Domain Resource API with the parameters of the request mapped to existing atoms, and deal with the returned result to decide if the web layers succeeds or fails the response it needs to send back.
408408

409409
##### 1.5.4.1 Atomized Attributes
410410

411-
The use of atomized attributes its not required to be introduced to existing code, but its recommend that at some point to refactor the existing code to use them, if not already done.
411+
The use of atomized attributes is not required to be introduced to existing code, but it's recommended that at some point to refactor the existing code to use them, if not already done.
412412

413-
When this Architecture pattern is analyzed for the first time by an AI Coding Agent its recommended for it to check if the project is already using atomized parameters to call the Business Logic layer, and if not then ask the developer if he wants to use PLANNING.md to create an Intent with the tasks to implement it, or if he wants to do it himself. The Intent **MUST** be created as specified by the INTENT_SPECIFICATION.md and exemplified by the INTENT_EXAMPLE.md.
413+
When this Architecture pattern is analyzed for the first time by an AI Coding Agent it's recommended for it to check if the project is already using atomized parameters to call the Business Logic layer, and if not then ask the developer if he wants to use PLANNING.md to create an Intent with the tasks to implement it, or if he wants to do it himself. The Intent **MUST** be created as specified by the INTENT_SPECIFICATION.md and exemplified by the INTENT_EXAMPLE.md.
414414

415415
##### 1.5.4.2 Example of calling the a Domain Resource API from LiveView
416416

417-
LiveVew mode trimmed to show only the code for the `edit` handle event, that's enough to illustrate the call to the Domain Resource API with atomized attributes:
417+
LiveView mode trimmed to show only the code for the `edit` handle event, that's enough to illustrate the call to the Domain Resource API with atomized attributes:
418418

419419

420420
```elixir
@@ -492,17 +492,17 @@ end
492492

493493
##### 1.5.4.4 Example to atomize and sanitize attributes
494494

495-
A better approach would be to use a dedicated struct for each Domain Resource accept input from the external world that atomizes the request input parameters into existing atoms and sanitizes their values.
495+
A better approach would be to use a dedicated struct for each Domain Resource to accept input from the external world that atomizes the request input parameters into existing atoms and sanitizes their values.
496496

497497
```elixir
498-
defmodule MyAppWeb.Catalogs.Products.CatalogProductInputSanitezed do
498+
defmodule MyAppWeb.Catalogs.Products.CatalogProductInputSanitized do
499499

500500
# Define here the struct.
501-
# THe struct **MUST** explicitly enforce the required attributes.
501+
# The struct **MUST** explicitly enforce the required attributes.
502502

503503
def sanitize_product_params(input_params) do
504504
# For each atom key in the struct we want to sanitize the input_params value
505-
# present in the correspondent string key.
505+
# present in the corresponding string key.
506506

507507
# You may want to just let it crash if the required keys aren't all present.
508508

@@ -514,7 +514,7 @@ end
514514

515515
## 2. Milliseconds Timestamps
516516

517-
By default Phoenix generates schemas and migration with timestamps in `:utc_datetime` which have a default precision of seconds, thus making impossible to order records in database queries by the insert or update times, because several records can be inserted or updated in the same second.
517+
By default Phoenix generates schemas and migrations with timestamps in `:utc_datetime` which have a default precision of seconds, thus making impossible to order records in database queries by the insert or update times, because several records can be inserted or updated in the same second.
518518

519519
The solution is to modify in `config/config.exs`, the `generators` configuration to use `timestamp_type: :utc_datetime_usec` and the code generators will use them by default when creating migrations and schemas.
520520

@@ -527,7 +527,7 @@ When this Architecture pattern is analyzed for the first time by an AI Coding Ag
527527

528528
For security reasons this architecture requires that all migrations and schemas use binary IDs for the primary key and foreign keys.
529529

530-
UUIDV7 as binary IDS are strongly recommended because they can be sorted in database queries, once they are time based.
530+
UUIDV7 as binary IDs are strongly recommended because they can be sorted in database queries, once they are time based.
531531

532532
When this Architecture pattern is analyzed for the first by an AI Coding Agent it **MUST** check `config/config.exs` to see if the project is already using binary IDs and if not then ask the developer if he wants to use PLANNING.md to create an Intent with the tasks to implement it, or if he wants to do it himself. The Intent **MUST** be created as specified by the INTENT_SPECIFICATION.md and exemplified by the INTENT_EXAMPLE.md.
533533

0 commit comments

Comments
 (0)