This repo is inspired by a 2023 paper titled Towards Modern Development of Cloud Applications released by a team at Google.
.NET’s host runtime model and built-in dependency injection makes building scalable “modular monoliths” easier than ever. This lets teams — especially startups — move faster with less development, deployment, and operational friction while still maintaining many of the benefits of microservices such as independent scaling of services, isolation of responsibilities, and so on.
⮑ Read more here: https://chrlschn.dev/blog/2024/01/a-practical-guide-to-modular-monoliths/
💡 This sample repo is a very "naive" implementation that uses simple database level signaling. In a more robust system, we could use Postgres queues or an external service bus like SQS or Google Pub/Sub.
To run the sample in this repository, you'll need to have Docker installed.
There are two modes:
- Development mode. In this mode, we run all of the services in one single host
- Runtime mode. In this mode, we run each of the services in separate hosts
To run in development mode:
# Start the Postgres container
docker compose up
# API is at port 5228
cd src/core
dotnet run
In this mode, the Docker Compose file is responsible only for running our Postgres containers. We load all of our services into a single .NET host runtime which includes our REST API as well as our two background services which perform notification and updates to the Project
.
To run in runtime mode:
# API is at port 8080
docker compose -f docker-compose-run.yaml up --build
In this mode, the Docker Compose file is responsible for building and running 4 containers:
- Postgres
- The .NET host for the API; we don't load the other services
- The .NET host for the service to notify
WorkItem
collaboratorUser
s - The .NET host for the service to check and update the
Project
for aWorkItem
if all of theProject
'sWorkItem
s are completed.
Each container contains a host that loads a slice of the workload.
To test the example, use the following URLs to access the Swagger UI:
# In development mode
http://localhost:5228/swagger
# In runtime mode
http://localhost:8080/swagger
From here, follow these steps:
- Add a Project and get the ID from the response
- Add a User and keep the ID of the user
- Add a Work Item and link it to the Project and User
- The service
WorkItemMonitorService
will detect the new Work Item and simulate sending notifications to the collaborators - Update the status of the Work Item to Completed
- The service
WorkItemStatusMonitorService
will detect changes in Work Item status and check the Project to see if all of the Work Items are completed and if so, update the status of the Project to Completed as well.
To execute this in curl
:
# Add the project and note the response ID
curl -X 'POST' \
'http://localhost:5228/api/projects/add' \
-H 'accept: text/plain' \
-H 'Content-Type: application/json' \
-d '"Test"'
# Assume we get an ID of 8b90f42c-af9f-4c61-9b18-52e764f0ca8c
# Add a user:
curl -X 'POST' \
'http://localhost:5228/api/users/add' \
-H 'accept: text/plain' \
-H 'Content-Type: application/json' \
-d '{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "User 1",
"createdUtc": "2024-01-14T23:45:38.591Z",
"email": "[email protected]"
}'
# Now add a work item using the project ID and user ID:
curl -X 'POST' \
'http://localhost:5228/api/tasks/add' \
-H 'accept: text/plain' \
-H 'Content-Type: application/json' \
-d '{
"projectId": "8b90f42c-af9f-4c61-9b18-52e764f0ca8c",
"name": "Task 1",
"collaborators": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
}'
# Assume we get an ID of: faba6123-183d-405d-8751-9736ed4a243b
# This will log:
# [NEW] Found new task Task 1 on project Test
# ⮑ Notifying user User 1
# Finally, let's update the status with our work item ID:
curl -X 'POST' \
'http://localhost:5228/api/tasks/faba6123-183d-405d-8751-9736ed4a243b/status' \
-H 'accept: text/plain' \
-H 'Content-Type: application/json' \
-d '"Completed"'
Console output when a new task is detected:
Console output when the task status is updated to Completed
: