Project Title: Online Skill Assessment Platform
Requirement/Objective
Build a online Skill Assessment platform where Admins can create tests and Candidates can take them.
Functional Requirements
- The system supports two roles: Admin and Candidate, each with specific functionalities.
π¨βπΌ Admin Functionalities:
- Register and authenticate securely.
- Create, update, and delete Tests.
- Create and manage Questions (e.g., multiple-choice, true/false).
- Associate questions with specific tests.
- View all candidate Submissions and their Scores after test completion.
π§βπ Candidate Functionalities:
- Register and authenticate securely.
- View a list of available tests.
- Start and complete a test within the specified time limit.
- Submit answers to questions.
- View their final score after the test has been evaluated.
Approach:
Let’s put Architect’s hat now and think of some Non functional requirement.
- Scalability– Since many users can take test simultaneously we need to ensure application is scalable.
- Async processing : Test scoring should be asynchronous to avoid any bottleneck .It will allow candidate’s test submission to be instant as they donβt wait for score calculation. Improves user experience.
- Maintainability We will breakdown the services into separate services like TestService, SubmissionService, and ScoringService which will promote high cohesion and low coupling, aligning each service with a specific business capability. This enables independent deployment, scaling, and fault isolation, which enhances overall system flexibility, resilience, and team productivity.
- Security: The project uses JWT-based authentication to securely identify users and ensure stateless communication between client and services. Role-based authorization is implemented using claims in the JWT to restrict access to specific features for Admins and Candidates.
In the first phase, we will be working only the backend services (will leave UI for the 3rd phase). All the testing will be done either by using Unit Tests or using Postman / Swagger endpoints. To avoid exposing all these services to UI, we will create a BFF service(Backend-for-Frontend (BFF)) . It will help keep frontend simple and clean without having to call multiple services. It will allow use to optimize responses and API shape for frontend needs. In this phase ,these BFF service will internally call other 3 services(call it backend services as it is in backend and not exposed to client application)
π‘A BFF (Backend for Frontend) is an architectural pattern designed to create a dedicated backend layer for each specific frontend (user interface).It provides a tailored backend that handles requests from a single frontend client, like a mobile app or a web application i.e. if you want to develop both Mobile App and Web application you can create two separate BFF , one for Web and other for Mobile. These will internally call other backend services and return in the format the Frontend application expects.
In the 2nd phase, we will implement a Message Broker using RabbitMQ to ensure services communicate asynchronously. This will help decouple the services, centralize orchestration logic, and improve reliability, fault tolerance, and scalability by enabling retry mechanisms, message durability, and event-driven communication between microservices like SubmissionService and ScoringService.
π‘In a microservices architecture, it's not ideal for the UI to call multiple microservices directly due to complexity and coupling. Instead, we use an API Gateway or a Backend-for-Frontend (BFF) pattern to centralize communication, handle cross-cutting concerns, and simplify frontend logic. This ensures a more secure, maintainable, and scalable architecture
In the 3rd phase, we will develop a basic Angular frontend to integrate all services and complete the end-to-end implementation of the project.
π‘ Remember the following Terms we discussed above
- Scalability: It is the ability of a system to handle increased load or growth efficiently without degrading performance.
- Asynchronous Calls: It means non-blocking operation where the caller can continue execution without waiting for the task to complete. This is useful when you are performing long operations which should be done in the background to prevent user from waiting for the same.
- High Cohesion: High Cohesion ensures that each class has a single responsibility and that the methods within the class are closely related to that responsibility. This can be extended to Microservices architecture were we should breakdown and create services in such a way that each service should be responsible for a single, well-defined business capability. High Cohesion goes hand in hand with SRP (Single Responsibility Principle). When a class follows SRP, it would be also high cohesive because all the methods inside it are related to a single responsibility.
- Low Coupling: Each class should depend on few other classes (or dependencies) as possible. If and when dependencies are required, they should be through interfaces or abstract classes . Instead of directly instantiating objects of one class within another, use dependency injection to inject dependencies. This reduces tight coupling between classes, making the system more flexible.
Other interesting things to do:
Add API Management: It acts as a single entry point for the frontend. It can be used to handle cross cutting concerns like Authentication & Authorization, Rate Limiting , Logging Caching.