Skip to content

Commit 9743bbd

Browse files
authored
Merge pull request #24 from THC-Software/readme_changes
Readme changes
2 parents d582654 + f9cd43d commit 9743bbd

File tree

3 files changed

+666
-2
lines changed

3 files changed

+666
-2
lines changed

Deployment/pos-debezium.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ spec:
2525
- name: debezium-configmap
2626
configMap:
2727
name: debezium-configmap
28-
restartPolicy: Always
28+
restartPolicy: Always
2929
---
3030
apiVersion: v1
3131
kind: Service

README.md

+195-1
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,24 @@
33
## Deploying Kubernetes Manifests Locally with Minikube
44

55
### Prerequisites
6+
67
- [Install Minikube](https://minikube.sigs.k8s.io/docs/start/)
78
- [Install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
89

910
### Step-by-Step Guide
1011

1112
#### 1. Start Minikube
13+
1214
Start Minikube with sufficient resources.
15+
1316
```bash
1417
minikube start --memory=4096 --cpus=2
1518
```
1619

1720
#### 2. Apply kubernetes Manifests
21+
1822
Use kubectl to apply each of these YAML files. This will create the necessary Kubernetes resources.
23+
1924
```bash
2025
kubectl apply -f redis.yml
2126
kubectl apply -f pos_postgres_write.yml
@@ -25,19 +30,208 @@ kubectl apply -f pos_service.yml
2530
```
2631

2732
#### 3. Check the status of pods/services
33+
2834
Check the status of your pods and services to ensure they are running correctly.
35+
2936
```bash
3037
kubectl get pods
3138
kubectl get services
3239
```
3340

3441
#### 4. Access the service
42+
3543
Minikube provides a way to access services running inside the cluster using minikube service.
44+
3645
```bash
3746
minikube service pos-service
3847
```
3948

4049
You can also use `kubectl port-forward` to forward a port from your local machine to a port on a pod. For example:
50+
4151
```bash
4252
kubectl port-forward deployment/pos-service 8080:8080
43-
```
53+
```
54+
55+
## API Documentation
56+
We provided a OpenAPI documentation for the PlayOfferService.
57+
It can be found in the [`openapi.json`](./openapi.json) file.
58+
59+
## Used patterns in microservice
60+
**All `.cs` files are linked to the respective file in the project**
61+
62+
### Saga
63+
The Saga pattern is used to maintain data consistency in a microservice architecture. It is a sequence of local transactions where each transaction updates the data and publishes a message or event to trigger the next transaction in the sequence. If a transaction fails, the saga executes a series of compensating transactions that undo the changes that were made by the preceding transactions.
64+
65+
In the PlayOfferService the Saga Pattern is used in conjunction with the CourtService, to automatically create a Reservation if a PlayOffer is joined by a second Member. It includes the following Steps:
66+
67+
1. After `JoinPlayOfferCommand` is received, the PlayOfferService publishes a `PlayOfferJoinedEvent`
68+
2. The CourtService listens to the `PlayOfferJoinedEvent` and tries to create a Reservation for the PlayOffer at the specified time in the PlayOfferJoinedEvent
69+
3. The CourtService publishes one of three possible events, each containing the `EventId` of the `PlayOfferJoinedEvent` in the `CorrelationId`:
70+
- `ReservationCreatedEvent` if the Reservation was successfully created
71+
- `ReservationRejectedEvent` if the Reservation could not be created (e.g. no court available)
72+
- `ReservationLimitExceededEvent` if the Reservation could not be created due to a limit of Reservations per Member
73+
4. The PlayOfferService listens to the events published by the CourtService and reacts depending on the event:
74+
- If a `ReservationCreatedEvent` is received it then triggers a `PlayOfferReservationAddedEvent` in the PlayOfferService to add the Reservation to the respective PlayOffer
75+
- If a `ReservationRejectedEvent` or `ReservationLimitExceededEvent` is received it then triggers a `PlayOfferOpponentRemovedEvent` to revert the changes of the `PlayOfferJoinedEvent`
76+
77+
The **compensation logic** for the Saga is implemented in the [ReservationEventHandler](./Application/Handlers/Events/ReservationEventHandler.cs) File in the functions in lines _81 - 102_.
78+
79+
### CQRS
80+
The CQRS pattern is used in the PlayOfferService to separate the read and write operations for PlayOffers. The write operations are implemented using commands, which are located in the [Commands](./Application/Commands) folder. The read operations are implemented using queries, which are located in the [Queries](./Application/Queries) folder.
81+
Each query and command is then handled by their respective handlers, which are located in the root of the [Handlers](./Application/Handlers) folder. Each handler is responsible for executing the logic for a specific command or query.
82+
83+
#### Queries
84+
The following queries are implemented in the PlayOfferService with their respective handlers:
85+
- [`GetPlayOffersByClubIdQuery.cs`](./Application/Queries/GetPlayOffersByClubIdQuery.cs)(_Line 1:8_): Returns all PlayOffers for a specific Club. The query is created and sent to the handler in the [`PlayOfferController.cs`](./Application/Controllers/PlayOfferController.cs)(_Line 40_).
86+
- [`GetPlayOffersByClubIdHandler.cs`](./Application/Handlers/GetPlayOffersByClubIdHandler.cs)(_Line 1:49_): Handles the `GetPlayOffersByClubIdQuery` and returns the PlayOffers for the specified Club
87+
- [`GetPlayOffersByParticipantIdQuery.cs`](./Application/Queries/GetPlayOffersByParticipantIdQuery.cs)(_Line 1:8_): Returns all PlayOffers for a specific participant (either as creator or opponent). The query is created and sent to the handler in the [`PlayOfferController.cs`](./Application/Controllers/PlayOfferController.cs)(_Line 64_).
88+
- [`GetPlayOffersByParticipantIdHandler.cs`](./Application/Handlers/GetPlayOffersByParticipantIdHandler.cs)(_Line 1:49_): Handles the `GetPlayOffersByParticipantIdQuery` and returns the PlayOffers for the specified participant
89+
- [`GetPlayOffersByCreatorNameQuery.cs`](./Application/Queries/GetPlayOffersByCreatorNameQuery.cs)(_Line 1:8_): Returns a specific PlayOffer by the name of it's creator. The query is created and sent to the handler in the [`PlayOfferController.cs`](./Application/Controllers/PlayOfferController.cs)(_Line 91_).
90+
- [`GetPlayOffersByCreatorNameHandler.cs`](./Application/Handlers/GetPlayOffersByCreatorNameHandler.cs)(_Line 1:59_): Handles the `GetPlayOfferByCreatorNameQuery` and returns the PlayOffer with the specified Id
91+
92+
#### Commands
93+
The following commands are implemented in the PlayOfferService with their respective handlers:
94+
- [`CancelPlayOfferCommand.cs`](./Application/Commands/CancelPlayOfferCommand.cs)(_Line 1:7_): Cancels a PlayOffer. The command is created and sent to the handler in the [`PlayOfferController.cs`](./Application/Controllers/PlayOfferController.cs)(_Line 158_).
95+
- [`CancelPlayOfferHandler.cs`](./Application/Handlers/CancelPlayOfferHandler.cs)(_Line 1:79_): Handles the `CancelPlayOfferCommand` and cancels the PlayOffer
96+
- [`CreatePlayOfferCommand.cs`](./Application/Commands/CreatePlayOfferCommand.cs)(_Line 1:7_): Creates a new PlayOffer. The command is created and sent to the handler in the [`PlayOfferController.cs`](./Application/Controllers/PlayOfferController.cs)(_Line 128_).
97+
- [`CreatePlayOfferHandler.cs`](./Application/Handlers/CreatePlayOfferHandler.cs)(_Line 1:87_): Handles the `CreatePlayOfferCommand` and creates a new
98+
- [`JoinPlayOfferCommand.cs`](./Application/Commands/JoinPlayOfferCommand.cs)(_Line 1:7_): Joins a PlayOffer. The command is created and sent to the handler in the [`PlayOfferController.cs`](./Application/Controllers/PlayOfferController.cs)(_Line 192_).
99+
- [`JoinPlayOfferHandler.cs`](./Application/Handlers/JoinPlayOfferHandler.cs)(_Line 1:100_): Handles the `JoinPlayOfferCommand` and joins the PlayOffer
100+
101+
102+
#### Projection
103+
In the PlayOfferService, projections are implemented using the **Mediator Pattern** which is implemented, in dedicated `EventHandlers` for each entity, in the [Events](./Application/Handlers/Events) folder.
104+
105+
Each Entity has a dedicated `RedisStreamReader` which subscribes to the Redis stream and listens to, filters and parses the events for a specific entity:
106+
- [`PlayOfferEventHandler.cs`](./Application/Handlers/Events/PlayOfferEventHandler.cs)(_Line 1:94_): Handles the events for the `PlayOffer` entity
107+
- [`MemberEventHandler.cs`](./Application/Handlers/Events/MemberEventHandler.cs)(_Line 1:126_): Handles the events for the `Member` entity
108+
- [`ReservationEventHandler.cs`](./Application/Handlers/Events/ReservationEventHandler.cs)(_Line 1:151_): Handles the events for the `Reservation` entity
109+
- [`CourtEventHandler.cs`](./Application/Handlers/Events/CourtEventHandler.cs)(_Line 1:57_): Handles the events for the `Court` entity
110+
- [`ClubEventHandler.cs`](./Application/Handlers/Events/ClubEventHandler.cs)(_Line 1:116_): Handles the events for the `Club` entity
111+
112+
The `EventHandlers` receive their events from the `RedisStreamService` and then apply the events to the respective entity:
113+
- [`RedisClubStreamService.cs`](./Application/RedisClubStreamService.cs)(_Line 1:82_): Read the events from the redis club stream and sends them to the `ClubEventHandler`
114+
- [`RedisCourtStreamService.cs`](./Application/RedisCourtStreamService.cs)(_Line 1:83_): Read the events from the redis court stream and sends them to the `CourtEventHandler`
115+
- [`RedisMemberStreamService.cs`](./Application/RedisMemberStreamService.cs)(_Line 1:86_): Read the events from the redis member stream and sends them to the `MemberEventHandler`
116+
- [`RedisPlayOfferStreamService.cs`](./Application/RedisPlayOfferStreamService.cs)(_Line 1:68_): Read the events from the redis play offer stream and sends them to the `PlayOfferEventHandler`
117+
- [`RedisReservationStreamService.cs`](./Application/RedisReservationStreamService.cs)(_Line 1:76_): Read the events from the redis reservation stream and sends them to the `ReservationEventHandler`
118+
119+
### Event Sourcing
120+
The write side of the CQRS implementation is using a event sourcing pattern. In the PlayOfferService, events are used to represent changes to the state of Entities.
121+
When a command is received, it is validated and then converted into one or more events, which are then stored in the write side database.
122+
123+
The events are structured with a hierarchy of event classes:
124+
- `Technical[...]Event`: Represents a group of events that are used for a specific entity, these are used to route the events to the correct `EventHandler` in the read model. Implements the `BaseEvent` class.
125+
- [`TechnicalPlayOfferEvent.cs`](./Domain/Events/PlayOffer/TechnicalPlayOfferEvent.cs)(_Line 1:7_): Represents the events for the `PlayOffer` entity
126+
- [`TechnicalMemberEvent.cs`](./Domain/Events/Member/TechnicalMemberEvent.cs)(_Line 1:7_): Represents the events for the `Member` entity
127+
- [`TechnicalReservationEvent.cs`](./Domain/Events/Reservation/TechnicalReservationEvent.cs)(_Line 1:8_): Represents the events for the `Reservation` entity
128+
- [`TechnicalCourtEvent.cs`](./Domain/Events/Court/TechnicalCourtEvent.cs)(_Line 1:7_): Represents the events for the `Court` entity
129+
- [`TechnicalClubEvent.cs`](./Domain/Events/Club/TechnicalClubEvent.cs)(_Line 1:7_): Represents the events for the `Club` entity
130+
- [`BaseEvent.cs`](./Domain/Events/BaseEvent.cs)(_Line 1:34_): Represents the whole event including the following metadata:
131+
Each event class represents a specific type of event that can occur in the system.
132+
- `event_id`: The unique identifier for the event
133+
- `entity_id`: The unique identifier for the entity that the event belongs to
134+
- `event_type`: The type of the event
135+
- `entity_type`: The type of the entity that the event belongs to
136+
- `timestamp`: The timestamp when the event occurred
137+
- `correlation_id`: The correlation id of the event
138+
- [`DomainEvent.cs`](./Domain/Events/DomainEvent.cs)(_Line 1:35_): Is used as the data type of the `eventData` property in the `BaseEvent` class. It is also used for json serialization and deserialization.
139+
140+
The smallest unit of events can be found in the [Events](./Domain/Events) folder. Each event class represents a specific type of event that can occur in the system and implements the `DomainEvent` class.
141+
- **PlayOfferEvents**:
142+
- [`PlayOfferCreatedEvent.cs`](./Domain/Events/PlayOffer/PlayOfferCreatedEvent.cs)(_Line 1:26_): Represents the event when a PlayOffer is created
143+
- [`PlayOfferJoinedEvent.cs`](./Domain/Events/PlayOffer/PlayOfferJoinedEvent.cs)(_Line 1:9_): Represents the event when a Opponent joins a PlayOffer
144+
- [`PlayOfferCancelledEvent.cs`](./Domain/Events/PlayOffer/PlayOfferCancelledEvent.cs)(_Line 1:6_): Represents the event when a PlayOffer is canceled
145+
- [`PlayOfferReservationAddedEvent.cs`](./Domain/Events/PlayOffer/PlayOfferReservationAddedEvent.cs)(_Line 1:6_): Represents the event when a Reservation was created by the court service and was now added to the PlayOffer
146+
- [`PlayOfferOpponentRemovedEvent.cs`](./Domain/Events/PlayOffer/PlayOfferOpponentRemovedEvent.cs)(_Line 1:5_): Represents the event when no Reservation could be created by the court service and therefore the opponent was removed from the PlayOffer
147+
148+
149+
- **MemberEvents**:
150+
- [`MemberCreatedEvent.cs`](./Domain/Events/Member/MemberCreatedEvent.cs)(_Line 1:13_): Represents the event when a Member is created
151+
- [`MemberDeletedEvent.cs`](./Domain/Events/Member/MemberDeletedEvent.cs)(_Line 1:5_): Represents the event when a Member is deleted
152+
- [`MemberEmailChangedEvent.cs`](./Domain/Events/Member/MemberEmailChangedEvent.cs)(_Line 1:6_): Represents the event when the email of a Member is changed
153+
- [`MemberFullNameChangedEvent.cs`](./Domain/Events/Member/MemberFullNameChangedEvent.cs)(_Line 1:8_): Represents the event when the name of a Member is changed
154+
- [`MemberLockedEvent.cs`](./Domain/Events/Member/MemberLockedEvent.cs)(_Line 1:6_): Represents the event when a Member is locked
155+
- [`MemberUnlockedEvent.cs`](./Domain/Events/Member/MemberUnlockedEvent.cs)(_Line 1:6_): Represents the event when a Member is unlocked
156+
157+
158+
- **ReservationEvents**:
159+
- [`ReservationCreatedEvent.cs`](./Domain/Events/Reservation/ReservationCreatedEvent.cs)(_Line 1:21_): Represents the event when a Reservation is created
160+
- [`ReservationCancelledEvent.cs`](./Domain/Events/Reservation/ReservationCancelledEvent.cs)(_Line 1:5_): Represents the event when a Reservation is canceled
161+
- [`ReservationLimitExceededEvent.cs`](./Domain/Events/Reservation/ReservationLimitExceededEvent.cs)(_Line 1:21_): Represents the event when the limit of Reservations per Member is exceeded
162+
- [`ReservationRejectedEvent.cs`](./Domain/Events/Reservation/ReservationRejectedEvent.cs)(_Line 1:6_): Represents the event when a Reservation could not be created
163+
164+
165+
- **CourtEvents**:
166+
- [`CourtCreatedEvent.cs`](./Domain/Events/Court/CourtCreatedEvent.cs)(_Line 1:14_): Represents the event when a Court is created
167+
- [`CourtUpdatedEvent.cs`](./Domain/Events/Court/CourtUpdatedEvent.cs)(_Line 1:12_): Represents the event when a Court is changed
168+
169+
170+
- **ClubEvents**:
171+
- [`ClubCreatedEvent.cs`](./Domain/Events/Club/ClubCreatedEvent.cs)(_Line 1:13_): Represents the event when a Club is created
172+
- [`ClubDeletedEvent.cs`](./Domain/Events/Club/ClubDeletedEvent.cs)(_Line 1:16_): Represents the event when a Club is deleted
173+
- [`ClubNameChangedEvent.cs`](./Domain/Events/Club/ClubNameChangedEvent.cs)(_Line 1:6_): Represents the event when the name of a Club is changed
174+
- [`ClubLockedEvent.cs`](./Domain/Events/Club/ClubLockedEvent.cs)(_Line 1:6_): Represents the event when a Club is locked
175+
- [`ClubUnlockedEvent.cs`](./Domain/Events/Club/ClubUnlockedEvent.cs)(_Line 1:6_): Represents the event when a Club is unlocked
176+
177+
178+
The events are applied to the entities in the `apply` methods, the implementation location can be found under [Domain Driven Design](#domain-driven-design).
179+
180+
#### Idempotent Events
181+
In the PlayOfferService, the idempotency of all events is guaranteed!
182+
183+
All events which were read from the redis stream and were processed by the `EventHandlers` are saved into the `AppliedEvents` table in the read side database. This allows us to check if a received event was already processed and therefore can be ignored.
184+
Therefore the outcome of all events won't change if they are processed multiple times.
185+
186+
### Authentication and Authorization
187+
In the PlayOfferService, Authentication and Authorization are implemented using a JWT token, which is is provided by the club service. All requests to the PlayOfferService must include a valid JWT token in the Authorization header.
188+
189+
All Queries can be executed by users with the `ADMIN` and `MEMBER` role. The commands can only be executed by users with the `MEMBER` roles.
190+
A custom [`JwtClaimsMiddleware.cs`](./JwtClaimsMiddleware.cs)(_Line 1:43_) is used to extract the claims from the JWT token and add them to the `HttpContext` of the request.
191+
192+
These claims are then checked with the `Authorize` attribute in the [`PlayOfferController.cs`](./Application/Controllers/PlayOfferController.cs)(_Lines 31,55,80,115,147,181_) to ensure that the user has the necessary roles to execute the request.
193+
Furthermore, most requests also extract the `memberId` and/or the `clubId` from the claims to ensure that the user can only access their own data, this can be seen in [`PlayOfferController.cs`](./Application/Controllers/PlayOfferController.cs)(_Lines 39,63,122:123,154,189_).
194+
195+
### Optimistic Locking
196+
In the PlayOfferService, Optimistic Locking is implemented using the `EFCore` and its transaction mechanism. When a request is received, the current amount of events is read and incremented by one.
197+
198+
When the request is processed, the amount of events is read again and compared to the initial amount. If the amount of events has changed unexpectedly during the transaction, a concurrency exception is thrown and the transaction rolled back.
199+
200+
Otherwise the transaction is committed and the changes are saved to the database.
201+
202+
The Optimistic Locking is implemented in the each CommandHandler in the [Commands](./Application/Handlers) folder.
203+
204+
- [`CancelPlayOfferHandler.cs`](./Application/Handlers/CancelPlayOfferHandler.cs)
205+
- _Line 26:27_
206+
- _Line 67:75_
207+
- [`JoinPlayOfferHandler.cs`](./Application/Handlers/JoinPlayOfferHandler.cs)
208+
- _Line 29:30_
209+
- _Line 88:96_
210+
- [`CreatePlayOfferHandler.cs`](./Application/Handlers/CreatePlayOfferHandler.cs)
211+
- _Line 69:70_
212+
- _Line 75:83_
213+
214+
### Domain Driven Design
215+
In the PlayOfferService, DDD is used to model the core domain of the application, which includes the following entities:
216+
217+
- [`PlayOffer.cs`](./Domain/Models/PlayOffer.cs)(_Line 1:81_): Represents a play offer that is created by a member and can be joined by other members
218+
- [`Member.cs`](./Domain/Models/Member.cs)(_Line 1:81_): Represents a member of the platform who can create and join play offers
219+
- [`Reservation.cs`](./Domain/Models/Reservation.cs)(_Line 1:51_): Represents a reservation for a play offer that is created by the court service
220+
- [`Court.cs`](./Domain/Models/Court.cs)(_Line 1:45_): Represents a court that can be reserved for a play offer
221+
- [`Club.cs`](./Domain/Models/Club.cs)(_Line 1:66_): Represents a club that can have multiple courts and members
222+
223+
Since event sourcing was also used each entity implements a `apply` method which is used to apply the events to the entity. It is important to note that the `apply` method is not allowed to fail, as it is used to reconstruct the state of the entity and the correctness of the events is guaranteed by the `CommandHandlers
224+
`.
225+
The implementation for the `apply` methods can be found here:
226+
- [`PlayOffer.cs`](./Domain/Models/PlayOffer.cs)(_Line 23_)
227+
- [`Member.cs`](./Domain/Models/Member.cs)(_Line 17_)
228+
- [`Reservation.cs`](./Domain/Models/Reservation.cs)(_Line 18_)
229+
- [`Court.cs`](./Domain/Models/Court.cs)(_Line 14_)
230+
- [`Club.cs`](./Domain/Models/Club.cs)(_Line 14_)
231+
232+
However, we didn't implement a `process` method in each entity, since the processing of the events is done in the `CommandHandlers`.
233+
234+
### Transaction Log Trailing
235+
In the PlayOfferService, Transaction Log Trailing is implemented using Debezium, which is an open-source platform for change data capture. Debezium captures changes to the PostgreSQL database and publishes them to a Redis Stream.
236+
237+
The Debezium configuration can be found in the [pos_debezium.yml](./debezium-conf/application.properties)(_Line 1:21_) file.

0 commit comments

Comments
 (0)