- Introduction
- Pre-requisite
- Module anatomy
- Local Run
- Sequence Diagram
- Steps to verify the flow
- Design Decisions
- Assumptions
- In-progress items
A student management and fee collection system. Consists of 3 micro-services for student, payment & receipt domains and a supporting kafka server.
Please make sure your machine has the following things installed,
-
IDE: IntelliJ IDEA (preferred)
Java 17
(can be installed easily using IDE if you have one)Maven 3.8.*
(again comes bundled with most of the IDE)- Checkout this project as whole
- It is a multi-module mono-repo consists of all the required modules together.
- Select maven's
clean install
goals from maven section of the IDE
(or)
-
If you prefer terminal, you can use
mvnw
(maven wrapper) comes with this repository itself./mvnw clean install
from the root of the directory to build all the modules.
- All the common modules are group under
common
maven module. - Service modules are suffixed with
**-service
&**-server
- can be run individually
kafka-server
module is aEmbedded Kafka
setup using spring-boot. And it is good enough for this demo app.- All the required kafka
topics
are created automatically when it is up. - It is visible only on
localhost
and not configured for remote execution. - Message serialization:
Json
serializer is used for messages. Due to demo app, we haven't gone withAvro
format &schema-registry
.
- All the required kafka
Steps to locally run the required applications,
-
Please run the below services,
-
Using maven wrapper (maven wrapper doesn't need maven installation locally)
-
Step 1: Open
terminal
and move to theskiply-system
directorycd {your-location}/skiply-system
-
Step 2: Run each of the below commands on a separate terminal,
./mvnw clean spring-boot:run --file ./kafka-server/pom.xml
- recommended to start this first./mvnw clean spring-boot:run --file ./student-service/pom.xml
./mvnw clean spring-boot:run --file ./payment-service/pom.xml
./mvnw clean spring-boot:run --file ./receipt-service/pom.xml
(or)
-
-
Using IDE as spring boot application,
-
-
Once all the services are up & running, then go to service specific Swagger UI to execute the flows as described below.
sequenceDiagram
participant api as api-consumer
participant student as student-service
participant payment as payment-service
participant receipt as receipt-service
participant kafka as kafka-server
participant mpg as mock-payment-gateway
Note over kafka: In-memory Embedded Kafka <br/> Run as Spring Boot App
Note over mpg: It is just a runtime component <br/>with in "payment".<br/> Just separated for a clarity
api->>+student: POST /v1/students <br/>(register a student)
student->>student: persist student record in DB
student-->>-api: Http status: 201 Created (success)
api->>+payment: POST /v1/payments <br/>(payload with student-id)
payment->>+mpg: Validate card info and collect payment
mpg-->>-payment: success
payment->>payment: persist payment transaction <br/> record in DB
payment-->>kafka: Publishes "payment-success" event <br/> (with Transaction & Purchase details)
payment-->>-api: HTTP status: 201 Created (success) <br/>(payload with payment reference number)
kafka-->>+receipt: Consumes "payment-success" event
receipt->>receipt: Persistes the receipt record in DB <br/> with PENDING status
receipt-->>-kafka: Publishes "student-info-request" event
kafka-->>+student: Consumes "student-info-request" event
student->>student: Retrieve student info record from DB
student-->>-kafka: Publishes "student-info-response" event
kafka-->>+receipt: Consumes "student-info-response" event
receipt->>-receipt: Update the receipt record with student info in DB <br/> with COMPLETED status
api->>+receipt: GET /v1/receipts?paymentReferenceNumber=xxxxx
receipt->>receipt: Retrieve receipt record by payment reference number
receipt-->>-api: 200 Ok (success) <br/> (payload with receipt details)
-
Step 1: Register a student using student-service's post endpoint (Assumption: Student Id has to be provided to register).
-
Step 2: Make a payment for the registered student-id. Again you should get
201
and apaymentReferenceNumber
as a response. Note:IdempotencyKey
has to be unique for each transaction to maintain the idempotency of the payment request. -
Step 3: Retrieve the receipt using
paymentReferenceNumber
- To aggregate data from multiple service, the approach I took here is
CQRS pattern.
- For instance,
receipt-service
needs data fromstudent-service
&payment-service
. The flow is,payment-service
emits an event tokafka
with required information once payment is collected successfully.receipt-service
then consumes it and load the data in a format it needs (since the payment data is immutable once created). It is a perfectly fine to capture and store locally in a structure it needs.- Once payment related information is persisted in the
receipt-service
's local DB, then it will request for the student info. It is achieved by request-response event model usingkafka
. - Both payment & student information required for receipt will be stored locally in an expected format.
- For instance,
- Due to time constraint I haven't used the Outbox pattern before publishing the events.
- Used
@RequiredArgsConstructor
for constructor injection - Two type of service classes may have been used in some parts of service to comply with DDD approach
**ApplicationService
class - to execute application level business logics (akaUseCase
classes)**DomainService
class - to execute Domain business logics.- Optional if only one AggregateRoot is involved. Ex:
student-service
. - It is only applicable when two or more AggregateRoots has to be accessed and orchestrated to execute Domain logic (DDD concept).
- Optional if only one AggregateRoot is involved. Ex:
- Used ValueObjects to embrace domain driven concepts and set a meaningful types (DDD concept).
- ex:
StudentId
, etc
- ex:
- I used
Lombok
annotations in the Domain classes due to time constrain. Usually it will be made up of pure Java code without any dependency from an external libs or frameworks.
- Student number will be unique across the schools. So, make it as primary key after the required validations.
- Otherwise, we need to maintain internal student id with in our system to make it unique.
- Payment service doesn't validate the student Id is a registered one before making payment. It was clarified before doing this assignment.
- Unit testing