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
40 changes: 34 additions & 6 deletions modules/module-01/REFLECTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

# Module 1 — Reflection

**Team name**: **\*\***\_\_\_**\*\***
**Branch**: `module-01/<team-name>`
**Submitted**: before Module 2 lesson
**Team name**: **Iulian - just me**
**Branch**: `module-01/iulian`
**Submitted**: Unfortuneltey a bit late 👉👈

---

Expand All @@ -22,8 +22,17 @@ 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:_
```
It solves a problem for the developer who has to change code.

In a monolith, changing the logging logic means opening the same codebase
that handles login, games, and notifications. You risk breaking something
unrelated every time you deploy.

With separate services, you change one thing, deploy one thing, and nothing
else is at risk.

```
---

## 2. Your choice
Expand All @@ -34,7 +43,16 @@ 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:_
```
The line between activity-service and logging-service.

They both deal with what users do, but for completely different reasons.
activity-service powers the social feed. logging-service exists because of
GDPR — it has to check consent before writing anything down.

If I merged them, a bug in the feed logic could accidentally bypass the
consent check. Two very different responsibilities tangled in one place.
```

---

Expand All @@ -46,7 +64,17 @@ 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:_
```
In the monolith, "did this user consent AND what did they play today" is
one SQL query joining two tables.

In the distributed design, that data lives in two separate services that
cannot touch each other's database. A simple question became a distributed
systems problem.

I don't have a solution yet — I think that's what the rest of the course
is for.
```

---

Expand Down
61 changes: 52 additions & 9 deletions modules/module-01/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@ 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)_ | | | |
## Iulian ##

| Bounded Context | Responsibilities | Owned Entities | Team |
| --------------- | -------------------------------------------------------- | ----------------| ----------- |
| Identity | Manages who users are, handles registration and profiles | User, Session | Platform |
| Game Library | Manages the game catalog, genres, search and game metadata | Game, Genre, Tag | Catalog |
| Activity | Records what users play, follow and rate, serves the social feed | ActivityEvent, Follow, Rating | Social |
| Logging | Stores GDPR consent status, records activity only for opted-in users | ConsentRecord, ActivityLog | Compliance |
| Notification | Delivers alerts via push and email, manages notification preferences | Notification, NotificationPreference | Platform |


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

Expand All @@ -45,6 +50,29 @@ For each pair of services that need to communicate, define:
- **Protocol**: REST or event (async)
- **Payload**: key fields exchanged

## Iulian ##

**Flow 1**

- **Direction**: client -> gateway -> auth-service
- **Trigger**: User submits login form
- **Protocol**: REST (synchronous or how it is written) - Client is waiting for the token
- **Payload**: {email, password} -> returns {jwt_token}

**Flow 2**

- **Direction**: activity-service -> logging-service
- **Trigger**: An activity event is recorded
- **Protocol**: RabbitMQ message - activity-service shouldn't wait or fail if logging is slow
- **Payload**: {activity_id, user_id, action, game_id, timestamp}

**Flow 3**

- **Direction**: activity-service -> notification-service
- **Trigger**: A followed froend starts playing
- **Protocol**: Still a RabbitMQ message - Notification dlelivery can be slow so like this the activity write is not blocked
- **Payload**: {email, password} -> returns {jwt_token}

Example:

```
Expand All @@ -69,25 +97,40 @@ Draw the full GameHub service map:

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

## It is made in DrawIO ##

---

## 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 microservices have the best tools fot your job. Notification delivery involves holding many open connections and Node.js has the event loop (we did this in Server-side JS) which was built for this. Python would work, but Node.js is more natural here. The key insight is that services are isolated enough that a different language in one place doesn't contaminate the others.


2. What is the risk of `activity-service` calling `logging-service` synchronously — why might you prefer an async event instead?

- If logging-service is slow, or down, activity-service would be blocked waiting or it would fail. Every user action would feel not-user-friendly, and a logging outage would break the entire activity feature. Using an async event instead, activity-service fires the event and moves on immediately. Logging-service processes it when it can. The two services fail independently (unfortunetley).

3. Why does `logging-service` need a GDPR consent check before recording any activity?

- EU law requires that you have the user's explicit consent before tracking their behavior (GDPR class). If you record first and check later, you've already violated the regulation. Logging-service is the gatekeeper: it receives the activity event, checks its own consent table for that user, and only writes the record if consent exists. The consent data lives in logging-service's database and nobody else owns it.

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

---

## Minimum to submit this branch

- [ ] Bounded context table filled in (at least 4 services justified)
- [ ] At least 3 service contracts defined
- [ ] Service map committed (sketch, photo, or ASCII)
- [ ] `REFLECTION.md` completed and committed
- [x] Bounded context table filled in (at least 4 services justified)
- [x] At least 3 service contracts defined
- [x] Service map committed (sketch, photo, or ASCII)
- [x] `REFLECTION.md` completed and committed

The map does not need to be perfect. It needs to be yours.


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

**Team name**: _______________
**Branch**: `module-02/<team-name>`
**Submitted**: before Module 3 lesson
**Team name**: Just me
**Branch**: `module-02/iulian`
**Submitted**: After Module 3 lesson unfortlunetley 👉👈

---

Expand All @@ -18,7 +18,10 @@ 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:*
> If everything is in one file, swapping SQLite for PostgreSQL means
digging through HTTP handlers, business logic, and SQL all mixed together.
With layers, I only touch `database.py` and `repository.py` — the rest
doesn't know the database even exists.

---

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

Give a concrete scenario, not a general principle.

> *Your answer:*

> If another service wrote directly to the `games` table, it could insert
a game with a missing `genre` or a malformed `cover_url` that bypasses
the Pydantic validation in `game-service`. The data would land in the DB
in a broken state and my service would start returning garbage without
any error ever being raised.
---

## 3. The tradeoff
Expand All @@ -42,7 +48,13 @@ 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:*
> For two endpoints it feels like overkill — I wrote five files where one
would have worked. The cost is time and mental overhead just to add a
single field.

It starts paying off the moment a second person touches the code, or when
a test needs to mock just the repository without touching the HTTP layer.
At that point having clear boundaries is worth more than the extra files.

---

Expand Down
8 changes: 4 additions & 4 deletions modules/module-02/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ Also confirm `aiosqlite` is in your `requirements.txt` — the CI test runner us

## Minimum to submit this branch

- [ ] `game-service` running on port 8002 with all 4 endpoints working
- [ ] Alembic migration for the `games` table committed
- [ ] At least one test passing in `game-service/tests/`
- [ ] `REFLECTION.md` completed and committed
- [x] `game-service` running on port 8002 with all 4 endpoints working
- [x] Alembic migration for the `games` table committed
- [x] At least one test passing in `game-service/tests/`
- [x] `REFLECTION.md` completed and committed

If you run out of time: the search endpoint is optional. The other three are not.
149 changes: 149 additions & 0 deletions services/game-service/alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# A generic, single database configuration.

[alembic]
# path to migration scripts.
# this is typically a path given in POSIX (e.g. forward slashes)
# format, relative to the token %(here)s which refers to the location of this
# ini file
script_location = %(here)s/alembic

# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
# for all available tokens
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
# Or organize into date-based subdirectories (requires recursive_version_locations = true)
# file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s

# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory. for multiple paths, the path separator
# is defined by "path_separator" below.
prepend_sys_path = .


# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the tzdata library which can be installed by adding
# `alembic[tz]` to the pip requirements.
# string value is passed to ZoneInfo()
# leave blank for localtime
# timezone =

# max length of characters to apply to the "slug" field
# truncate_slug_length = 40

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false

# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false

# version location specification; This defaults
# to <script_location>/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "path_separator"
# below.
# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions

# path_separator; This indicates what character is used to split lists of file
# paths, including version_locations and prepend_sys_path within configparser
# files such as alembic.ini.
# The default rendered in new alembic.ini files is "os", which uses os.pathsep
# to provide os-dependent path splitting.
#
# Note that in order to support legacy alembic.ini files, this default does NOT
# take place if path_separator is not present in alembic.ini. If this
# option is omitted entirely, fallback logic is as follows:
#
# 1. Parsing of the version_locations option falls back to using the legacy
# "version_path_separator" key, which if absent then falls back to the legacy
# behavior of splitting on spaces and/or commas.
# 2. Parsing of the prepend_sys_path option falls back to the legacy
# behavior of splitting on spaces, commas, or colons.
#
# Valid values for path_separator are:
#
# path_separator = :
# path_separator = ;
# path_separator = space
# path_separator = newline
#
# Use os.pathsep. Default configuration used for new projects.
path_separator = os

# set to 'true' to search source files recursively
# in each "version_locations" directory
# new in Alembic version 1.10
# recursive_version_locations = false

# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8

# database URL. This is consumed by the user-maintained env.py script only.
# other means of configuring database URLs may be customized within the env.py
# file.
sqlalchemy.url = sqlite:///./games.db


[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples

# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks = black
# black.type = console_scripts
# black.entrypoint = black
# black.options = -l 79 REVISION_SCRIPT_FILENAME

# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
# hooks = ruff
# ruff.type = module
# ruff.module = ruff
# ruff.options = check --fix REVISION_SCRIPT_FILENAME

# Alternatively, use the exec runner to execute a binary found on your PATH
# hooks = ruff
# ruff.type = exec
# ruff.executable = ruff
# ruff.options = check --fix REVISION_SCRIPT_FILENAME

# Logging configuration. This is also consumed by the user-maintained
# env.py script only.
[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARNING
handlers = console
qualname =

[logger_sqlalchemy]
level = WARNING
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
1 change: 1 addition & 0 deletions services/game-service/alembic/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generic single-database configuration.
Loading