8 |
9 | Domain: |
10 | Ordering |
11 |
12 |
13 | Concepts: |
14 | Domain Driven Design (Subdomains, Bounded Contexts, Ubiquitous Language, Aggregates, Value Objects) |
15 |
16 |
17 | Architecture style: |
18 | Event Driven Microservices |
19 |
20 |
21 | Architectural patterns: |
22 | Event Sourcing, Hexagonal Architecture and CQRS |
23 |
24 |
25 | Technology: |
26 | Python with (FastAPI), MongoDB and Redis |
27 |
28 |
29 |
30 | ## Domain
31 | Customers use the website application(s) to place orders. Application coordinates order preparation and delivery.
32 |
33 | ## Sub-domains
34 | - **Order**: This domain is responsible for managing and processing orders placed by customers, including product selection, pricing, and payment completion.
35 | - **Product**: This domain is responsible for managing and providing information about the products available for sale, including descriptions, prices and images.
36 | - **Delivery**: This domain is responsible for managing and tracking deliveries, including scheduling deliveries, tracking carrier location, and processing returns.
37 | - **Maps**: This domain is responsible for providing geographic information including delivery routes, delivery locations and estimated travel time.
38 | - **Payment**: This domain is responsible for processing payments, including credit card authorizations, balance checks and payment confirmations
39 |
40 |
41 | ## Domain model
42 |
43 | Sub-domain *software programing* models:
44 |
45 | - [ordering](https://github.com/marcosvs98/hexagonal-architecture-with-python/tree/main/src/domain)
46 |
47 | > Domain model is mainly a software programing model which is applied to a specific sub-domain.
48 | > It defines the vocabulary and acts as a communication tool for everyone involved (business and IT), deriving a [Ubiquitous Language](https://martinfowler.com/bliki/UbiquitousLanguage.html).
49 |
50 | ## Bounded Context
51 |
52 | Each of this group of applications/services belongs to a specific bounded context:
53 | - [ordering](https://github.com/marcosvs98/hexagonal-architecture-with-python/tree/main/src/domain) - Order bounded context, with messages serialized to JSON
54 |
55 |
56 | > A goal is to develop a [Ubiquitous Language](https://martinfowler.com/bliki/UbiquitousLanguage.html) as our domain (sub-domain) model within an explicitly Bounded Context.
57 | > Therefore, there are a number of rules for Models and Contexts
58 | > - Explicitly define the context within which a model applies
59 | > - Ideally, keep one sub-domain model per one Bounded Context
60 | > - Explicitly set boundaries in terms of team organization, usage within specific parts of the application, and physical manifestations such as code bases and database schemas
61 |
62 |
63 |
64 | ## Technologies and patterns used
65 |
66 | - **Python 3.10**
67 | - **FastAPI (Rest API)**
68 | - **MongoDB**
69 | - **Pydantic 2.5**
70 | - **Redis**
71 | - **CQRS**
72 | - **Ports & Adapters**
73 | - **Event Sourcing**
74 |
75 | In addition to others, the main pattern that guides this project is the Ports & Adapter + CQRS. In summary, this pattern provides a way to organize code so that business logic is encapsulated, but separated from the underlying delivery mechanism. This allows for better maintenance and fewer dependencies.
76 |
77 | With CQRS, we have a clear separation of concerns, making the codebase easier to understand and maintain.Developers can more easily reason about how changes to the codebase will affect the overall system. Additionally, its use allows each model to evolve independently since they are not strongly coupled. This means changes to one model can be made without affecting the other model.
78 |
79 |
80 | ---
81 | ## Running the project
82 |
83 | ### Option 1 - Via Docker Compose
84 |
85 | #### Run docker-compose
86 |
87 | Finally, run the project and its dependencies in the background using the command
88 | ```bash
89 | docker-compose up -d
90 | ```
91 |
92 | ## References
93 |
94 | - [Hexagonal Architecture](https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/)
95 | - [Domain Driven Design - DDD](https://lyz-code.github.io/blue-book/architecture/domain_driven_design/)
96 | - [Repository Pattern](https://lyz-code.github.io/blue-book/architecture/repository_pattern/)
97 | - [Service Layer Pattern](https://www.cosmicpython.com/book/chapter_04_service_layer.html)
98 |
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.1'
2 |
3 | services:
4 | ordering-service:
5 | container_name: ordering-service
6 | build:
7 | context: .
8 | volumes:
9 | - .:/home/userapp/app/.
10 | env_file:
11 | - .env
12 | ports:
13 | - '8000:8000'
14 | environment:
15 | MONGO_SERVER: order-aggregate-repository_mongo-db
16 | MONGO_PORT: '27017'
17 | MONGO_USERNAME: root
18 | MONGO_PASSWORD: admin
19 | ENVIRONMENT: development
20 | restart: always
21 | depends_on:
22 | - order-aggregate-repository_mongo-db
23 | - ordering-event-bus
24 |
25 | order-aggregate-repository_mongo-db:
26 | image: mongo:5.0
27 | container_name: order-aggregate-repository_mongo-db
28 | environment:
29 | MONGO_INITDB_ROOT_USERNAME: admin
30 | MONGO_INITDB_ROOT_PASSWORD: root
31 | ports:
32 | - '27018:27017'
33 | restart: always
34 |
35 | ordering-event-store-repository_mongo-db:
36 | image: mongo:5.0
37 | container_name: ordering-event-store-repository_mongo-db
38 | environment:
39 | MONGO_INITDB_ROOT_USERNAME: admin
40 | MONGO_INITDB_ROOT_PASSWORD: root
41 | ports:
42 | - '27019:27017'
43 | restart: always
44 |
45 | ordering-redis:
46 | image: "redis:alpine"
47 | container_name: ordering-redis
48 | command: redis-server
49 | ports:
50 | - "6379:6379"
51 |
52 | ordering-event-store-repository_admin-mongo:
53 | image: 0x59/admin-mongo:latest
54 | container_name: ordering-event-store-repository_admin-mongo
55 | environment:
56 | PORT: 1234
57 | CONN_NAME: ordering-event-store-repository_mongo-db
58 | DB_HOST: 27017
59 | restart: unless-stopped
60 | ports:
61 | - "1234:1234"
62 | links:
63 | - ordering-event-store-repository_mongo-db
64 | depends_on:
65 | - ordering-event-store-repository_mongo-db
66 |
67 | zookeeper:
68 | image: confluentinc/cp-zookeeper:latest
69 | ports:
70 | - "2181:2181"
71 | environment:
72 | ZOOKEEPER_CLIENT_PORT: 2181
73 | ZOOKEEPER_TICK_TIME: 2000
74 |
75 | ordering-event-bus-kafka:
76 | image: confluentinc/cp-kafka:latest
77 | container_name: ordering-event-bus-kafka
78 | ports:
79 | - "9092:9092"
80 | environment:
81 | KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181"
82 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://ordering-event-bus-kafka:9092
83 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
84 | depends_on:
85 | - zookeeper
86 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | echo "Installing development dependencies..."
3 | poetry install -vvv
4 | echo "Installing development dependencies... Done!"
5 |
--------------------------------------------------------------------------------
/mypy.ini:
--------------------------------------------------------------------------------
1 | [mypy]
2 | python_version=3.10
3 | mypy_path=src
4 | incremental=True
5 | ignore_missing_imports=True
6 | follow_imports=skip
7 | check_untyped_defs=True
8 | warn_redundant_casts=True
9 | warn_unused_ignores=False
10 | no_implicit_reexport=False
11 | show_error_context=True
12 | strict_optional=True
13 | pretty=True
14 | plugins = pydantic.mypy
15 | show_error_codes = True
16 |
17 | [mypy-src.*.tests.*]
18 | allow_untyped_defs = True
19 | allow_untyped_calls = True
20 | disable_error_code = var-annotated, has-type, assignment
21 |
22 | [pydantic-mypy]
23 | init_forbid_extra = True
24 | init_typed = True
25 | warn_required_dynamic_aliases = True
26 | warn_untyped_fields = True
27 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "cqrs-architecture-with-python-event-sourcing"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Marcos Silveira <47537283+MarcosVs98@users.noreply.github.com>"]
6 | readme = "README.md"
7 | packages = [{include = "cqrs_architecture_with_python_event_sourcing"}]
8 |
9 | [tool.poetry.dependencies]
10 | python = "^3.9"
11 | aioredis = "^2.0.1"
12 | dnspython = "^2.4.2"
13 | fastapi = "^0.104.1"
14 | fastapi-pagination = "^0.12.12"
15 | motor = "^3.3.2"
16 | pydantic = "^2.5.1"
17 | pymongo = "^4.6.0"
18 | python-decouple = "^3.8"
19 | uvicorn = {extras = ["standard"], version = "^0.18.3"}
20 | requests = "^2.31.0"
21 | aiohttp = "^3.9.0"
22 | aiokafka = "^0.8.1"
23 |
24 |
25 | [build-system]
26 | requires = ["poetry-core"]
27 | build-backend = "poetry.core.masonry.api"
28 |
--------------------------------------------------------------------------------
/scripts/pre-commit/configure.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | echo "Inserindo hooks no git"
3 | pre-commit install --install-hooks
4 |
--------------------------------------------------------------------------------
/scripts/pre-commit/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | echo "Rodando pre-commit"
3 | pre-commit run --from-ref origin/HEAD --to-ref HEAD
4 |
--------------------------------------------------------------------------------
/scripts/pre-commit/run_all.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | echo "Rodando pre-commit"
3 | pre-commit run --all-files
4 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [tool:pytest]
2 | testpaths = tests
3 | addopts = -p no:warnings
4 | asyncio_mode = auto
5 | env =
6 | PYTEST_RUNNING = 1
7 |
8 | [coverage:run]
9 | branch = True
10 | omit =
11 | src/tests/*
12 | src/*/__init__.py
13 | src/database/migrations/*
14 | src/scripts/*
15 | src/*/ports/*
16 | source =
17 | ./src
18 |
19 | [pytest]
20 | asyncio_mode = auto
21 |
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | sonar.projectKey=