Skip to content

Commit c56a858

Browse files
committed
Refactor books domain and update documentation.
Do not run celery without explicit configuration Signed-off-by: Federico Busetti <[email protected]>
1 parent 0a8dc34 commit c56a858

25 files changed

+147
-81
lines changed

docker-compose.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ services:
1717
environment:
1818
OTEL_SERVICE_NAME: "bootstrap-fastapi-worker"
1919
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector:4317"
20+
CELERY__broker_url: "redis://redis:6379/0"
21+
CELERY__result_backend: "redis://redis:6379/1"
2022
volumes:
2123
- './src:/app'
2224
- './pyproject.toml:/app/pyproject.toml'
@@ -33,6 +35,8 @@ services:
3335
environment:
3436
OTEL_SERVICE_NAME: "bootstrap-fastapi-worker"
3537
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector:4317"
38+
CELERY__broker_url: "redis://redis:6379/0"
39+
CELERY__result_backend: "redis://redis:6379/1"
3640
volumes:
3741
- './src:/app'
3842
- './pyproject.toml:/app/pyproject.toml'
@@ -56,6 +60,8 @@ services:
5660
environment:
5761
OTEL_SERVICE_NAME: "bootstrap-fastapi-dev"
5862
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector:4317"
63+
CELERY__broker_url: "redis://redis:6379/0"
64+
CELERY__result_backend: "redis://redis:6379/1"
5965
ports:
6066
- '8000:8000'
6167
volumes:
@@ -86,6 +92,8 @@ services:
8692
environment:
8793
OTEL_SERVICE_NAME: "bootstrap-fastapi-http"
8894
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector:4317"
95+
CELERY__broker_url: "redis://redis:6379/0"
96+
CELERY__result_backend: "redis://redis:6379/1"
8997
ports:
9098
- '8001:8000'
9199
volumes:

docs/inversion-of-control.md

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,31 +38,36 @@ the concrete class whenever the protocol is expected:
3838
def main():
3939
Container()
4040
service = BookService()
41-
42-
# file `books/_data_access_interfaces.py`
41+
42+
43+
# file `books/_gateway_interfaces.py`
4344
class BookRepositoryInterface(Protocol):
4445
async def save(self, book: BookModel) -> BookModel:
4546
...
4647

47-
# file `domains/books/service.py`
48-
from domains.books._data_access_interfaces import BookRepositoryInterface
48+
49+
# file `domains/books/_service.py`
50+
from domains.books._gateway_interfaces import BookRepositoryInterface
4951
from dependency_injector.wiring import Provide, inject
5052

53+
5154
class BookService:
5255
book_repository: BookRepositoryInterface
5356

5457
@inject
5558
def __init__(
56-
self,
57-
book_repository: BookRepositoryInterface = Provide["book_repository"],
59+
self,
60+
book_repository: BookRepositoryInterface = Provide["book_repository"],
5861
) -> None:
5962
self.book_repository = book_repository
6063

64+
6165
# file `bootstrap/di_container.py`
6266
from sqlalchemy_bind_manager._repository import SQLAlchemyAsyncRepository
6367
from dependency_injector.containers import DeclarativeContainer
6468
from dependency_injector.providers import Factory
6569

70+
6671
class Container(DeclarativeContainer):
6772
# Note that dependency-injector package only allows string references
6873
book_repository: Factory[BookRepositoryInterface] = Factory(
@@ -122,24 +127,27 @@ concrete classes because nested imported modules, solving nothing.
122127
///
123128

124129
```python
125-
# file `domains/books/service.py`
130+
# file `domains/books/_service.py`
126131
from dependency_injector.containers import DynamicContainer
127-
from domains.books._data_access_interfaces import BookRepositoryInterface
132+
from domains.books._gateway_interfaces import BookRepositoryInterface
133+
128134

129135
class BookService:
130136
book_repository: BookRepositoryInterface
131137

132138
def __init__(
133-
self,
134-
container: DynamicContainer,
139+
self,
140+
container: DynamicContainer,
135141
) -> None:
136142
self.book_repository = container.book_repository()
137143

144+
138145
# entrypoint
139-
from domains.books.service import BookService
146+
from domains.books._service import BookService
140147
from dependency_injector.providers import Factory
141148
from sqlalchemy_bind_manager._repository import SQLAlchemyAsyncRepository
142149

150+
143151
def main():
144152
container = DynamicContainer()
145153
# Note that dependency-injector package only allows string references
@@ -198,16 +206,16 @@ and we don't end up in circular dependencies.
198206

199207
```python
200208
# file `bootstrap/factories.py`
201-
from domains.books._data_access_interfaces import BookRepositoryInterface
209+
from domains.books._gateway_interfaces import BookRepositoryInterface
202210

203211

204212
def book_repository_factory() -> BookRepositoryInterface:
205213
from sqlalchemy_bind_manager._repository import SQLAlchemyAsyncRepository
206214
return SQLAlchemyAsyncRepository()
207215

208216

209-
# file `domains/books/service.py`
210-
from domains.books._data_access_interfaces import BookRepositoryInterface
217+
# file `domains/books/_service.py`
218+
from domains.books._gateway_interfaces import BookRepositoryInterface
211219
from bootstrap.factories import book_repository_factory
212220

213221

docs/packages/bootstrap.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Bootstrap
22

3-
The `bootstrap` module contains logic that is shared among the external layer
3+
The `bootstrap` package contains logic that is shared among the external layer
44
(i.e. `http_app`, `celery_worker`, etc.).
55

6-
It contains 3 submodules (and related responsibilities):
6+
It contains the following submodules and packages (and related responsibilities):
77

88
* `bootstrap.bootstrap`: The application initialisation logic (database, logging,
99
celery tasks) necessary to run the domain logic. It uses `bootstrap.config` and

docs/packages/celery_worker.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Celery worker
22

3-
The `celery_worker` module is a small entrypoint to run Celery workers and beat.
3+
The `celery_worker` package is a small entrypoint to run Celery workers and beat.
44

55
The `Celery` class has to be initialised to invoke tasks from domain logic,
66
in addition to the worker, therefore we initialise it together with the generic

docs/packages/domains.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Domains
22

3-
The `domains` module contains all the application domain logic separated by domains
3+
The `domains` package contains all the application domain logic separated by domains
44
(this template provides a single domain: `books`).
55

66
Each domain should be self-contained and not invoke logic from other domains directly.
@@ -15,4 +15,34 @@ Using interfaces will:
1515

1616
## Book domain structure
1717

18-
[TODO] Refactor needed to implement a better structure to the domain
18+
The `domains.book` package provides an example implementation for a domain. It contains
19+
a list of public and protected modules.
20+
21+
Public package and modules are used by the application to invoke the
22+
domain functionalities:
23+
24+
* The main `domains.book` package provides the entrypoint for our application:
25+
the `BookService` class. We export it here from the `domains.book._service`
26+
module to hide protected entities that should not be accessed directly.
27+
* The `domains.book.interfaces` provides the `BookServiceInterface` protocol
28+
to be used for Inversion of Control (we don't currently use it in this
29+
application because Clean Architecture doesn't enforce Inversion of Control
30+
from the `http` application, and we don't have yet implemented other domains)
31+
* The `domains.book.dto` provides the data transfer objects required to invoke
32+
the `BookService` class.
33+
* The `domains.book.events` provides the event data structures that the domain
34+
is able to emit. They can be used by other domains to implement event handlers.
35+
36+
Protected package and modules are used by the implementation of the books domain
37+
and can be used to bootstrap the application:
38+
39+
* The `domains.book._gateway_interfaces` contains the gateway protocols against
40+
which the domain logic is implemented. We use them to configure the dependency
41+
injection container.
42+
* The `domains.book._models` contains the domain models. We use them also
43+
to bootstrap the SQLAlchemy imperative mapping in `bootstrap.storage.SQLAlchemy`
44+
package.
45+
* The `domains.book._service` contains the `BookService` implementation.
46+
* The `domains.book._tasks` contains the implementation of celery tasks
47+
for operations that can be queued without waiting for a result (e.g.
48+
send an email, invalidate cache).

docs/packages/gateways.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Gateways
22

3-
The `gateways` module contains the implementations of the drivers
3+
The `gateways` package contains the implementations of the drivers
44
handling communication with external services (i.e. database repositories,
55
event producers, HTTP clients).
66

docs/packages/http_app.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# HTTP App
22

3-
The `http_app` module contains the implementation of [FastAPI](https://fastapi.tiangolo.com/)
3+
The `http_app` package contains the implementation of [FastAPI](https://fastapi.tiangolo.com/)
44
framework and the logic relevant to HTTP communication (routes, graphql schema, HTTP error handling)

src/bootstrap/config.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
from pathlib import Path
3-
from typing import Dict, List, Literal
3+
from typing import Dict, List, Literal, Optional
44

55
import structlog
66
from opentelemetry import trace
@@ -18,11 +18,11 @@ class CeleryConfig(BaseModel):
1818
timezone: str = "UTC"
1919

2020
# Broker config
21-
broker_url: str = "redis://redis:6379/0"
21+
broker_url: Optional[str] = None
2222
broker_connection_retry_on_startup: bool = True
2323

2424
# Results backend config
25-
result_backend: str = "redis://redis:6379/1"
25+
result_backend: Optional[str] = None
2626
redis_socket_keepalive: bool = True
2727

2828
# Enable to ignore the results by default and not produce tombstones
@@ -36,14 +36,14 @@ class CeleryConfig(BaseModel):
3636
task_send_sent_event: bool = True
3737

3838
# Recurring tasks triggered directly by Celery
39-
# beat_schedule: dict = {}
40-
beat_schedule: dict = {
41-
"recurrent_example": {
42-
"task": "domains.books.tasks.book_cpu_intensive_task",
43-
"schedule": 5.0,
44-
"args": ("a-random-book-id",),
45-
},
46-
}
39+
beat_schedule: dict = {}
40+
# beat_schedule: dict = {
41+
# "recurrent_example": {
42+
# "task": "domains.books._tasks.book_cpu_intensive_task",
43+
# "schedule": 5.0,
44+
# "args": ("a-random-book-id",),
45+
# },
46+
# }
4747

4848

4949
class AppConfig(BaseSettings):

src/bootstrap/di_container.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from dependency_injector.containers import DeclarativeContainer, WiringConfiguration
22
from dependency_injector.providers import Dependency, Factory, Singleton
3-
from domains.books._data_access_interfaces import (
3+
from domains.books._gateway_interfaces import (
44
BookEventGatewayInterface,
55
BookRepositoryInterface,
66
)
7-
from domains.books.models import BookModel
7+
from domains.books._models import BookModel
88
from gateways.event import NullEventGateway
99
from sqlalchemy_bind_manager import SQLAlchemyBindManager
1010
from sqlalchemy_bind_manager.repository import SQLAlchemyAsyncRepository

src/bootstrap/storage/SQLAlchemy/default_bind_tables.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from domains.books.models import BookModel
1+
from domains.books._models import BookModel
22
from sqlalchemy import Column, Integer, String, Table
33
from sqlalchemy.orm import registry
44

0 commit comments

Comments
 (0)