Please follow along this guide to gain context on & test the applications present in this repository.
This project contains 2 services responsible for processing a charge initialization request, i.e.
- ChargeInitializer - Exposes a REST resource to process a charge initialization request. It validates the input & forwards the request to the authenticator service to be handled asynchronously.
- AclAuthenticator - Handles async requests by authenticating a charge session request & passing on the response to the provided callback.
The applications have been built using the following stack:
- Java as the programming language
- SpringBoot framework for executing the services
- Kafka for async communication
- H2 DataBase for storage layer
Requests are handled in the following manner:
- The client calls the
/chargeInitialize/startPOST endpoint to submit a request for charge session initialization. - This endpoint is hosted in the
ChargeInitializerservice, which handles the request by validating the input parameters, and on successful validation, produces aAclAuthenticatorQueuingMessage(AAQM) event to trigger the ACL authentication process in an async fashion. - The AAQM event is then consumed & processed by
AclAuthenticatorservice further, which first fetches the ACLs for both the station & driver entities from theCHARGE_SESSION_ACLtable to compute the final ACL permission. - This computed permission is then sent to the callback URL provided by the client.
- The computed permission, along with the response from the callback URL & the input details are stored in another table called
ACL_RESPONSE_AUDITto record the request details for auditability purpose.
Follow the below steps to perform one time setup of Kafka before booting the service:
- Install kafka version kafka_2.13-3.8.1 from https://kafka.apache.org/downloads (source download option)
- Navigate to the directory where kafka is installed & start Zookeeper
bin/zookeeper-server-start.sh config/zookeeper.properties
- Navigate to the directory where kafka is installed & start Kafka
bin/kafka-server-start.sh config/server.properties
- Once Kafka is up, create topic AclAuthenticatorQueuingMessage. This is required to be done only once.
bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic AclAuthenticatorQueuingMessage
- If required, you can check produced messages using the following command in the directory where kafka is installed. The following is the command to consume messages produced on our topic.
./bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic AclAuthenticatorQueuingMessage
Follow the below commands for both services to boot up the applications :
- Run commands from the previous section's step 2, followed by step 3 for getting Kafka up.
- Run
./gradlew buildto build application - Run
./gradlew bootRunto run service locally- The ChargeInitializer application will be hosted at
localhost:8080 - The AclAuthenticator application will be hosted at
localhost:8081
- The ChargeInitializer application will be hosted at
Once the applications are up, we can test them out by simply hitting POST for initializing charge request. This can be done through a tool like Postman or hitting the following curl commands from terminal.
Case 1) Happy case: Station ID & driver ID both are valid & have ALLOWED ACLs
Request to /chargeInitialize:
curl -H 'Content-Type: application/json' \
-d '{
"stationId":"123e4567-e89b-12d3-a456-426614174000",
"driverId":"maitreyi_sondhi_12345",
"callbackUrl":"http://localhost:8081/dummyCallback/post"
}' \
-X POST \
http://localhost:8080/chargeInitialize/start
Response from /chargeInitialize:
{
"status": "accepted",
"message": "Request is being processed asynchronously. The result will be sent to the provided callback URL."
}Logs from AclAuthenticator service for handling request:
2025-04-05T11:49:49.989+05:30 INFO 92548 --- [AclAuthenticator] [ntainer#0-0-C-1] h.AclAuthenticatorQueuingMessageConsumer : Received AAQM Event: {stationId='123e4567-e89b-12d3-a456-426614174000', driverId='maitreyi_sondhi_12345', callbackUrl='http://localhost:8081/dummyCallback/post', timestamp=1743833989983}
2025-04-05T11:49:49.994+05:30 INFO 92548 --- [AclAuthenticator] [ntainer#0-0-C-1] c.c.h.AuthenticationRequestHandler : Permission Status computed for the request: ALLOWED
2025-04-05T11:49:49.998+05:30 INFO 92548 --- [AclAuthenticator] [nio-8081-exec-4] c.c.controller.DummyCallbackController : Received request on dummy callback URL CallbackRequest{stationId='123e4567-e89b-12d3-a456-426614174000', driverToken='maitreyi_sondhi_12345', status=ALLOWED}
2025-04-05T11:49:49.999+05:30 INFO 92548 --- [AclAuthenticator] [ntainer#0-0-C-1] c.c.h.AuthenticationRequestHandler : Audit created for this request: AclResponseAudit{stationId='123e4567-e89b-12d3-a456-426614174000', driverId='maitreyi_sondhi_12345', callbackUrl='http://localhost:8081/dummyCallback/post', timestamp=1743833989983, permissionStatus=ALLOWED, callbackResponseStatus=201}
Case 2) Testing for negative case: Station ID & driver ID both are valid but driver ID does not have ALLOWED ACL.
Request to /chargeInitialize:
curl -H 'Content-Type: application/json' \
-d '{
"stationId":"123e4567-e89b-12d3-a456-426614174000",
"driverId":"someone_else_1234567890",
"callbackUrl":"http://localhost:8081/dummyCallback/post"
}' \
-X POST \
http://localhost:8080/chargeInitialize/start
Response from /chargeInitialize:
{
"status": "accepted",
"message": "Request is being processed asynchronously. The result will be sent to the provided callback URL."
}Logs from AclAuthenticator service for handling request:
2025-04-05T11:58:03.938+05:30 INFO 92548 --- [AclAuthenticator] [ntainer#0-0-C-1] h.AclAuthenticatorQueuingMessageConsumer : Received AAQM Event: {stationId='123e4567-e89b-12d3-a456-426614174000', driverId='someone_else_1234567890', callbackUrl='http://localhost:8081/dummyCallback/post', timestamp=1743834483910}
2025-04-05T11:58:03.943+05:30 INFO 92548 --- [AclAuthenticator] [ntainer#0-0-C-1] c.c.h.AuthenticationRequestHandler : Permission Status computed for the request: NOT_ALLOWED
2025-04-05T11:58:03.959+05:30 INFO 92548 --- [AclAuthenticator] [nio-8081-exec-6] c.c.controller.DummyCallbackController : Received request on dummy callback URL CallbackRequest{stationId='123e4567-e89b-12d3-a456-426614174000', driverToken='someone_else_1234567890', status=NOT_ALLOWED}
2025-04-05T11:58:03.963+05:30 INFO 92548 --- [AclAuthenticator] [ntainer#0-0-C-1] c.c.h.AuthenticationRequestHandler : Audit created for this request: AclResponseAudit{stationId='123e4567-e89b-12d3-a456-426614174000', driverId='someone_else_1234567890', callbackUrl='http://localhost:8081/dummyCallback/post', timestamp=1743834483910, permissionStatus=NOT_ALLOWED, callbackResponseStatus=201}
Case 3) Testing for invalid input: Station ID passed is not valid.
Request to /chargeInitialize:
curl -H 'Content-Type: application/json' \
-d '{
"stationId":"12345",
"driverId":"maitreyi_sondhi_12345",
"callbackUrl":"http://localhost:8081/dummyCallback/post"
}' \
-X POST \
http://localhost:8080/chargeInitialize/start
Response from /chargeInitialize:
{
"status": "bad_request",
"message": "Station ID passed is invalid, please retry with a valid UUID for Station ID."
}No Logs since input validation failed & request is not processed further.
Case 4) Testing for missing input data: Station ID & driver ID both are valid & but driver ID does not have any ACL information in DB.
Request to /chargeInitialize:
curl -H 'Content-Type: application/json' \
-d '{
"stationId":"123e4567-e89b-12d3-a456-426614174000",
"driverId":"someone_missing_from_DB_1234567890",
"callbackUrl":"http://localhost:8081/dummyCallback/post"
}' \
-X POST \
http://localhost:8080/chargeInitialize/start
Response from /chargeInitialize:
{
"status": "accepted",
"message": "Request is being processed asynchronously. The result will be sent to the provided callback URL."
}Logs from AclAuthenticator service for handling request:
2025-04-05T12:06:39.673+05:30 INFO 94600 --- [AclAuthenticator] [ntainer#0-0-C-1] h.AclAuthenticatorQueuingMessageConsumer : Received AAQM Event: {stationId='123e4567-e89b-12d3-a456-426614174000', driverId='someone_missing_from_DB_1234567890', callbackUrl='http://localhost:8081/dummyCallback/post', timestamp=1743834999648}
2025-04-05T12:06:39.686+05:30 INFO 94600 --- [AclAuthenticator] [ntainer#0-0-C-1] c.c.h.AuthenticationRequestHandler : Permission Status computed for the request: INVALID
2025-04-05T12:06:39.752+05:30 INFO 94600 --- [AclAuthenticator] [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-04-05T12:06:39.753+05:30 INFO 94600 --- [AclAuthenticator] [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2025-04-05T12:06:39.755+05:30 INFO 94600 --- [AclAuthenticator] [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
2025-04-05T12:06:39.783+05:30 INFO 94600 --- [AclAuthenticator] [nio-8081-exec-1] c.c.controller.DummyCallbackController : Received request on dummy callback URL CallbackRequest{stationId='123e4567-e89b-12d3-a456-426614174000', driverToken='someone_missing_from_DB_1234567890', status=INVALID}
2025-04-05T12:06:39.800+05:30 INFO 94600 --- [AclAuthenticator] [ntainer#0-0-C-1] c.c.h.AuthenticationRequestHandler : Audit created for this request: AclResponseAudit{stationId='123e4567-e89b-12d3-a456-426614174000', driverId='someone_missing_from_DB_1234567890', callbackUrl='http://localhost:8081/dummyCallback/post', timestamp=1743834999648, permissionStatus=INVALID, callbackResponseStatus=201}
No separate setup needs to be perfomed for DataBase, follow the below steps to view table data:
- Once the service AclAuthenticator is up, you can open the following link on Chrome -
http://localhost:8081/h2-console - Log in with the following credentials:
- Driver Class: org.h2.Driver
- JDBC URL: jdbc:h2:mem:aclDB
- User Name: sa
- Password : Keep this Empty
- Run the following at any time to view the state of the DB
SELECT * FROM ACL_RESPONSE_AUDIT;
SELECT * FROM CHARGE_SESSION_ACL;
- Any change to the DataBase can be made from the console provided in the above URL.
Schema for the 2 tables defined above are as follows:
Schema for CHARGE_SESSION_ACL : Stores ACLs for each entity. This entity could either be the driver, or the station.
CREATE TABLE CHARGE_SESSION_ACL
(
ENTITY_ID VARCHAR(100) PRIMARY KEY,
ENTITY_TYPE ENUM ('DRIVER', 'STATION'),
ACCESS ENUM ('ALLOWED', 'DENIED')
);Schema for ACL_RESPONSE_AUDIT : Stores each request's input & output data, persisted for tracking
CREATE TABLE ACL_RESPONSE_AUDIT
(
ID BIGINT PRIMARY KEY AUTO_INCREMENT,
STATION_ID VARCHAR(100),
DRIVER_ID VARCHAR(100),
CALLBACK_URL VARCHAR(100),
TIMESTAMP BIGINT,
PERMISSION_STATUS ENUM ('ALLOWED', 'NOT_ALLOWED', 'UNKNOWN', 'INVALID'),
CALLBACK_RESPONSE_STATUS INT
);To make these applications scalable the following enhancements can be made:
- Currently our applications are hosted locally on our system. We can autoscale the applications by deploying them online on Kubernetes containers.
- Currently we are using H2 for storing our data locally due to ease of set up. For productionization, we can use MySQL servers instead of H2 for a more scalable relation database.
- Replace the current mock ACL data with a mechanism to ingest new ACL permissions through a new REST endpoint.
- In the current setup, the callback URL handling is mocked as a REST endpoint
/dummyCallbackwithin theAclAuthenticatorapplication. It can be replaced with another Callback Service for handling GET & POST callback requests. - Unit Tests can be added to do testing of each component in an isolated fashion.
- An ETL pipeline can also be set up to perform auditing & data analysis on the tracking data. Currently this tracking data is ingested in the
ACL_RESPONSE_AUDITtable.
