Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions exit()
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
4af2fc1 (HEAD -> module-02/bahjat9, origin/module-02/bahjat9) Added Module 2 reflection answers
3c3f8ed Final sync for Module 2
482fe0d (module-01/bahjat9) Completed Module 2: Game Service API, Test, and Alembic
859407c (origin/module-01/bahjat9) complete module 01
c35ed89 (origin/main, origin/HEAD, module-01/Avengers, main) module 2
553e315 new strucures and files
b3a9e21 new models
7ae4d60 new models
3e5af4b new models
329d4c7 new models
f52cb8d new models
fbaa734 tech revision
b9d4b10 new file edits, md files
f07507e dependencies and installs
a33a249 dependencies and installs
eb73c21 dependencies and installs
c614bac dependencies and installs
5a7db1e dependencies and installs
697b867 dependencies and installs
3d16fb6 dependencies and installs
d804de5 feat: new installs
d26f3fe feat: new installs
5d0f3a5 feat: new installs
70e0a6c feat: new installs
35eae10 new installs and pers
aadbbeb new installs
171bbf7 new apps
6bcf2fc Add course adaptation guide for 30-hour EPITA condensed version
1b19be7 new domain and features
63d79a8 stiches and punts
dcd8c47 new pland and structure
dcf4163 new start
Empty file added gateway/app/__init__.py
Empty file.
24 changes: 24 additions & 0 deletions gateway/app/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
# Services available from Module 3
user_service_url: str = "http://localhost:8001"
game_service_url: str = "http://localhost:8002"
activity_service_url: str = "http://localhost:8003"

# Added in Module 4
# notification_service_url: str = "http://localhost:8004"

# Added in Module 5
# logging_service_url: str = "http://localhost:8006"

# Added in Module 6
# auth_service_url: str = "http://localhost:8005"
# secret_key: str = "dev-secret-change-in-production"

class Config:
env_file = ".env"


settings = Settings()
63 changes: 63 additions & 0 deletions gateway/app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import httpx
from fastapi import FastAPI, Request, Response

from app.config import settings

app = FastAPI(title="gateway", version="1.0.0")

# Routing table — maps the resource name in the URL path to the target service base URL.
# Path structure: /{version}/{resource}/...
# e.g. GET /v1/users/123 → resource = "users" → forward to user_service_url
#
# Add new entries here as each module introduces a new service.
# Module 4 will add: "notifications"
# Module 5 will add: "consent", "logs"
# Module 6 will add: "auth"
ROUTES: dict[str, str] = {
"users": settings.user_service_url,
"games": settings.game_service_url,
"activities": settings.activity_service_url,
}


@app.get("/health")
async def health():
"""
Gateway liveness check. Handled here — never forwarded to a service.
In Module 10 this endpoint will be upgraded to fan out to all services
and return their individual status.
"""
return {"status": "ok", "service": "gateway"}


@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
async def proxy(request: Request, path: str):
segments = path.split("/")
if len(segments) < 2:
return Response(status_code=404, content="Not found")

resource = segments[1]
target_base = ROUTES.get(resource)

if not target_base:
return Response(status_code=404, content=f"Unknown resource: {resource}")

target_url = f"{target_base}/{path}"

try:
async with httpx.AsyncClient(timeout=10.0) as client:
response = await client.request(
method=request.method,
url=target_url,
headers=request.headers.raw,
content=await request.body(),
params=request.query_params,
)
return Response(
content=response.content,
status_code=response.status_code,
headers=dict(response.headers),
media_type=response.headers.get("content-type"),
)
except httpx.RequestError:
return Response(status_code=503, content="Service unavailable")
4 changes: 4 additions & 0 deletions gateway/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fastapi==0.115.0
uvicorn[standard]==0.30.6
httpx==0.27.2
pydantic-settings==2.4.0
10 changes: 5 additions & 5 deletions modules/module-01/REFLECTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

# Module 1 — Reflection

**Team name**: **\*\***\_\_\_**\*\***
**Branch**: `module-01/<team-name>`
**Team name**: Bahjat
**Branch**: `module-01/<bahjat>`
**Submitted**: before Module 2 lesson

---
Expand All @@ -22,7 +22,7 @@ You started from a painful monolith. Now you're splitting it into separate servi

Think about it from three angles: the developer who has to change code, the team that has to deploy it, and the user who has to live with its failures. You don't need to cover all three, pick the one that felt most real to you today.

> _Your answer:_
> Splitting the app solves the problem of deployment crashes and tight coupling. From a user's perspective, if the Game Library goes offline because of a bug or an update, the whole platform doesn't crash. They can still log in, view their profile, and chat with friends because the Identity and Activity services are running independently.

---

Expand All @@ -34,7 +34,7 @@ Look at your service map. Every arrow between two services is a decision someone

What would break, slow down, or become harder to manage if you merged those two services back together?

> _Your answer:_
>I separated the Activity service from the Notification service. In the monolith, these were tightly coupled, meaning when a user logged a game, the system paused to write notifications for all their friends before finishing the request. By separating them and using an async event, logging an activity is instant, and notifications are processed safely in the background.

---

Expand All @@ -46,7 +46,7 @@ Microservices solve the monolith's problems. But they create new ones.

No need to solve it: just name it honestly. This is exactly the tension the rest of the course is about.

> _Your answer:_
>Data querying is much harder now. In the monolith, if I wanted to show an activity feed, I could just write a single SQL JOIN to get the user's name, the game title, and the activity. Now, that data is locked in three different databases, requiring multiple network calls between services just to render one page.

---

Expand Down
47 changes: 41 additions & 6 deletions modules/module-01/exercise.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Module 1 — Service Decomposition

**Duration**: 2h in class
**Branch to submit**: `module-01/<team-name>`
**Branch to submit**: `module-01/<bahjat>`

---

Expand All @@ -26,11 +26,13 @@ A bounded context is a part of the system that has a clear responsibility and ow

For each bounded context you identify, fill in the table:

| Bounded Context | Responsibilities | Owned Entities | Team |
| --------------- | -------------------------------------------------------- | -------------- | ----------- |
| Identity | Manages who users are, handles registration and profiles | User, Session | Platform |
| Game Library | _(fill in)_ | _(fill in)_ | _(fill in)_ |
| _(add more)_ | | | |
| Bounded Context | Responsibilities | Owned Entities | Team |
| Identity | Manages who users are, handles registration and profiles | User, Session | Platform |
| Game Library | Manages the catalog of games, titles, and genres | Game, Category | Content |
| Activity | Manages social feeds, friendships, and logging what people play | Activity, Friendship | Social |
| Notification | Manages delivering alerts and messages to users | Notification, DeviceToken | Communications |
| Logging | Records system events, checks GDPR consent | Log, ConsentRecord | Infra |


There is no single correct answer: what matters is that you can justify each row.

Expand All @@ -56,6 +58,24 @@ Payload: { activity_id, user_id, action, game_id, timestamp }

Focus on the flows that feel non-obvious. You do not need to document every possible pair.

Contract 1:
identity-service → activity-service
Trigger: a user logs in successfully
Protocol: RabbitMQ message (async)
Payload: { user_id, session_id, timestamp }

Contract 2:
activity-service → notification-service
Trigger: a user earns an achievement or milestone
Protocol: RabbitMQ message (async)
Payload: { user_id, achievement_id, message, timestamp }

Contract 3:
gateway → identity-service
Trigger: a client sends a login or registration request
Protocol: REST (sync)
Payload: { username, email, password_hash }

---

## Task 3 — Draw the service map _(~20 min)_
Expand All @@ -69,15 +89,30 @@ Draw the full GameHub service map:

This can be a sketch on paper, a whiteboard photo, or ASCII art committed to your branch.

[Client]
|
[Gateway]
_____|__|__|__|_____
| | | | |
[identity][game][activity][notification][logging]
|_async__| |___async___|___async___|


---

## Discussion _(~15 min)_

Three questions to discuss as a team before you leave:

1. Why does `notification-service` use Node.js instead of Python like the rest? What does that tell you about microservices and technology choices?
Because node handles lots of simultaneous I/O better. Each service can use whatever language fits its job.

2. What is the risk of `activity-service` calling `logging-service` synchronously — why might you prefer an async event instead?
if logging-service crashes, activity-service crashes too. Async decouples them.

3. Why does `logging-service` need a GDPR consent check before recording any activity?
you can't store personal data without consent. Logging is the last gate before data is written.


You do not need to write these answers down — they are warm-up for your REFLECTION.md.

Expand Down
10 changes: 5 additions & 5 deletions modules/module-02/REFLECTION.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Module 2 — Reflection

**Team name**: _______________
**Branch**: `module-02/<team-name>`
**Team name**: Bahjat
**Branch**: `module-02/<bahjat>`
**Submitted**: before Module 3 lesson

---
Expand All @@ -18,7 +18,7 @@ You built a service with distinct layers: models, schemas, repository, service,

Think about what happens six months later when someone new joins the team, or when you need to swap SQLite for PostgreSQL. What does the layered structure protect you from?

> *Your answer:*
> Splitting the code into layers protects the application from massive rewrites. If we decide to swap from SQLite to PostgreSQL later, we only have to update the repository.py and database.py files. The routing (routes.py) and business logic (service.py) don't care what database is used, so they remain completely untouched. It also makes testing easier because you can test business logic without needing a live database.

---

Expand All @@ -30,7 +30,7 @@ Each service owns its data exclusively — no other service is allowed to touch

Give a concrete scenario, not a general principle.

> *Your answer:*
> If the Activity Service was allowed to write directly to the games table in the Game Service database, it might bypass important validation rules (like adding a game without a cover_url). Even worse, if the Game Service team updates their database schema (like changing the column name title to game_name), the Activity Service's hardcoded SQL query would instantly break and crash without the Game Service team even knowing why.

---

Expand All @@ -42,7 +42,7 @@ You now have models, schemas, a repository, a service, and routes — five layer

And at what point does the complexity start to pay off? Where is the tipping point?

> *Your answer:*
>The cost is massive boilerplate. To do a simple SELECT * FROM games, you have to write code across five different files, which feels incredibly slow for a basic CRUD app. This complexity only pays off when the app scales — when you start adding caching, role-based permissions, or external API calls, having dedicated layers keeps the codebase from turning into spaghetti.

---

Expand Down
2 changes: 1 addition & 1 deletion modules/module-02/exercise.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Module 2 — FastAPI Service Design

**Duration**: 2h in class
**Branch to submit**: `module-02/<team-name>`
**Branch to submit**: `module-02/<bahjat>`

---

Expand Down
Binary file added services/activity-service/activities.db
Binary file not shown.
Loading