Skip to content

Latest commit

 

History

History
317 lines (252 loc) · 10.8 KB

File metadata and controls

317 lines (252 loc) · 10.8 KB

Caterpillar Truck Robot – REST API Server

This is a Spring Boot 3.x (Java 17) REST API that simulates a toy truck robot moving around on a square or rectangle table.

The entire API surface is defined in an OpenAPI 3.0 specification (src/main/resources/openapi/truck-robot.yaml).

During the Maven build, the openapi-generator-maven-plugin automatically generates:

  • Data models (SimulationResponse, CommandRequest, etc.)
  • REST controller interfaces (with CompletableFuture return types for async rest-api, or plain objects for sync rest-api)

I just implemented those generated interfaces from OpenAPI

Prerequisites

  • Java 17
  • Maven 3.9+
  • Free port (default: 8081 for async-soln1, 8082 for async-soln2 and 8083 for sync-slon3)

Verify: java -version mvn -version

Building

Full build + tests

mvn clean package mvn clean install

Faster — skip tests during dev

mvn clean package -DskipTests

Runtime Profiles

The application supports three mutually exclusive Spring profiles:

- async-soln1

  1. Asynchronous REST implementation (solution 1) \
  2. that uses my custom built WorkflowParallelSequencer \
  3. built on top of LMAX Disruptor/RingBuffer - avoids expensive locking(threads never block during request event submission), pre-allocated events for low GC \
  4. all workflow events belonging a sequenceKey (robot) are guaranteed to be sequenced on to single lane (disruptor/ringbuffer) \
  5. hash bucket index = sequenceKey modulo concurrencyLevel which is configurable \
  6. this sequecing avoids the need to implement locking / any expensive thread-safety mechanims on a Robot StateMachine \
  7. scales by parallelizing workflows of unrealated sequenceKeys but by guaranteed sequencing of all workflow events pertaining to the same sequenceKey (robotId in our case) \
  8. accordingly the RobotStores are also sharded \
  9. refer to CoreWorkflowParallelSequencer.java and LMAXDisruptorWorkflowSequencer.java in the project java project caterpillar-commons-lib - these are custom implemented by me \

- async-soln2

  1. Asynchronous REST implementation (solution 2)
  2. that uses my custom built ParallelSequenceExecutor
  3. built on top of JDKs SingleThreadPoolExecutors
  4. follows similar parallel-sequecing mechanism as async-soln1 but it uses the standard JDK's Single ThreadPoolExecutor(poolSize=1) instead of LMAX disruptor/ringbuffer
  5. a single ThreadPool executor gives the same sequenncing semantics - all tasks submitted to it are guranteed to be executed in sequence \
  6. so it also scales, but weaker than the async-soln1 because the source of serialization can still be contending for the task-queue (a blocking queue) when there hashing collisions
  7. accordingly the RobotStores are also sharded
  8. refer to ParallelSequenceExecutor.java and ParallelSequenceThreadPoolExecutor.java in the project java project caterpillar-commons-lib - these are custom implemented by me

- sync-soln3

  1. Synchronous REST implementation (solution 3)
  2. that does NOT use any of the above sequencing mechansims and does not scale as well above the above mechanisms
  3. since it does not do sequencing, we will have to guard the Robot's StateMachine during single or batch commands using a thick lock.
  4. accordingly the RobotStores are sharded
  5. however it doesnt just give up but tries to fare way better by lock-stripping to guard each partition separately with a thick partition lock
  6. also the locks used are Rentrant & FAIR locks to allow for reasonable fairness among the requests

Exactly one of these profiles must be active at runtime or the application will fail to run throwing an exception.

Option A: Run using exec-maven-plugin

The pom.xml defines exec-maven-plugin executions that start the server with the correct JVM flags and profile.
From the CLI:
async-soln1
mvn -DskipTests exec:exec@robot-restapi-async-soln1

async-soln2
mvn -DskipTests exec:exec@robot-restapi-async-soln2

sync-soln3
mvn -DskipTests exec:exec@robot-restapi-sync-soln3

Stop the server with Ctrl + C.

Option B: Run using spring-boot:run

From the CLI: \

async-soln1
mvn -DskipTests spring-boot:run -Dspring-boot.run.profiles=async-soln1

async-soln2
mvn -DskipTests spring-boot:run -Dspring-boot.run.profiles=async-soln2

sync-soln3
mvn -DskipTests spring-boot:run -Dspring-boot.run.profiles=sync-soln3

Verifying the Server is Running

On startup you should see a log similar to: The following 1 profile is active: "async-soln1"

Swagger UI: http://localhost:8081/swagger-ui/index.html

OpenAPI JSON: http://localhost:8081/v3/api-docs

Overriding Server Port

-Dspring-boot.run.arguments=--server.port=8082

spring-boot:run example: mvn spring-boot:run -Dspring-boot.run.profiles=async-soln1 -Dspring-boot.run.arguments=--server.port=8082

REST API Overview

Base path: /api/v1
All endpoints return JSON. Error responses follow this shape: JSON{ "error": { "code": "INVALID_REQUEST", "message": "some descriptive text", "traceId": "abc123..." } }

1. Create Simulation

POST /api/v1/simulations Optional: specify table size (defaults to 5×5).
Bashcurl -X POST http://localhost:8081/api/v1/simulations
-H "Content-Type: application/json"
-d '{ "table": { "length": 5, "breadth": 5 } }' → Returns 201 Created + Location header

2. Get Simulation State

GET /api/v1/simulations/{simulationId}
Bashcurl http://localhost:8081/api/v1/simulations/1

3. Delete Simulation

DELETE /api/v1/simulations/{simulationId}
Bashcurl -X DELETE http://localhost:8081/api/v1/simulations/1

4. Submit Single Command

POST /api/v1/simulations/{simulationId}/commands Supported commands: PLACE, MOVE, LEFT, RIGHT, REPORT Optional idempotency: Idempotency-Key header Examples: Bash# PLACE curl -X POST http://localhost:8081/api/v1/simulations/1/commands
-H "Content-Type: application/json"
-d '{ "type": "PLACE", "x": 0, "y": 0, "facing": "NORTH" }'

5. MOVE

curl -X POST http://localhost:8081/api/v1/simulations/1/commands
-H "Content-Type: application/json"
-d '{"type": "MOVE"}'

6. REPORT

curl -X POST http://localhost:8081/api/v1/simulations/1/commands
-H "Content-Type: application/json"
-d '{"type": "REPORT"}' Response always includes final report:

"0,3,NORTH" or "ROBOT_NOT_PLACED"

7. Submit Batch of Commands

POST /api/v1/simulations/{simulationId}/commands:batch Up to 25 commands, executed sequentially, single final report returned.
Bashcurl -X POST http://localhost:8081/api/v1/simulations/1/commands:batch
-H "Content-Type: application/json"
-d '{ "commands": [ {"type": "PLACE", "x": 1, "y": 2, "facing": "EAST"}, {"type": "MOVE"}, {"type": "LEFT"}, {"type": "REPORT"} ] }'

Implementation Notes

My code is mostly self explanatory.

Robot's StateMachine:

From caterpillar-truck-robot java project
com.caterpillar.robotics.truck.domain.robot

  1. Robot.java
  2. RobotStateSnapshot.java
  3. RobotTest.java (extensive unit tests)

From caterpillar-commons-lib (Generic libraries that can be used in any context)
com.caterpillar.commons.util.spatial.twod

  1. CoordinateSpace.java
  2. Direction.java
  3. MutablePositionVector.java
  4. PositionVector.java
  5. PositionVectorOps.java
  6. PositiveQuadrantGrid.java

Solution1 -async-soln1

From caterpillar-truck-robot java project \

  1. TruckRobotAsyncRestController.java
  2. RobotSimulationService.java
  3. ParallelSeqSequencerRobotSimulationService.java
  4. RobotWorkflowService.java
  5. CoreRobotWorkflowService.java
  6. InMemoryRobotStore.java
  7. RobotStore.java
  8. ShardedRobotStore.java

ThreadingModel: From caterpillar-commons-lib (Generic libraries that can be used in any context) \

  1. CoreWorkflowParallelSequencer.java
  2. LMAXDisruptorWorkflowSequencer.java
  3. SequencedEventsProcessor.java
  4. SequencerEvent.java
  5. WorkflowParallelSequencer.java
  6. WorkflowSequencer.java

Domain Model \

  1. RobotCommand.java
  2. RobotCommandType.java
  3. RobotWorkflowEvent.java
  4. RobotWorkflowEventType.java
  5. ZeroGCRobotWorkflowEvent.java

Spring Wiring \

  1. Soln1AppConfig.java
  2. CommonConfig.java
  3. SequencedServiceCommonConfig.java

Solution2 -async-soln2

From caterpillar-truck-robot java project \

  1. TruckRobotAsyncRestController.java
  2. RobotSimulationService.java
  3. ParallelSeqSequencerRobotSimulationService.java
  4. RobotWorkflowService.java
  5. CoreRobotWorkflowService.java
  6. InMemoryRobotStore.java
  7. RobotStore.java
  8. ShardedRobotStore.java

ThreadingModel: From caterpillar-commons-lib (Generic libraries that can be used in any context) \

  1. ParallelSequenceExecutor.java
  2. ParallelSequenceThreadPoolExecutor.java
  3. ExecutorServices.java
  4. ExecutorUtils.java

Domain Model \

  1. RobotCommand.java
  2. RobotCommandType.java
  3. RobotWorkflowEvent.java
  4. RobotWorkflowEventType.java
  5. GarbagyRobotWorkflowEvent.java

Spring Wiring \

  1. Soln2AppConfig.java
  2. CommonConfig.java
  3. SequencedServiceCommonConfig.java

Solution3 -sync-soln3

From caterpillar-truck-robot java project \

  1. TruckRobotSyncRestController.java
  2. RobotSimulationService.java (com.caterpillar.robotics.truck.solution3.api.syncrest)
  3. RobotWorkflowService.java
  4. PartitionLockGuardedRobotWorkflowService.java
  5. InMemoryRobotStore.java
  6. RobotStore.java
  7. ShardedRobotStore.java

Domain Model \

  1. RobotCommand.java
  2. RobotCommandType.java
  3. RobotWorkflowEvent.java
  4. RobotWorkflowEventType.java
  5. GarbagyRobotWorkflowEvent.java

Spring Wiring \

  1. Soln3AppConfig.java
  2. CommonConfig.java

Api2Domain and Domain2Api conversions and exception handling

  1. Api2DomainUtils.java
  2. ApiExceptionHandler.java
  3. Domain2ApiUtils.java

Known Garbage Objects

  1. Have annotated with custom @Garbage annotation to mark that I know this code-block generates garbage objects.

Other implementation approaches considered - but DE-SCOPED for the assignment

  1. Spring WebFlux based implementation - reactive-style
  2. GRPC equivalent of the REST-API
  3. Automated JBehave scenario tests
  4. Completely low GC approaches (async requests & responses correlated, the flow allowing for a fully ZERO GC approach atleast in the app layer, not in SpringMVC though which is not in our control)
  5. Replacing the heavy weight TOMCAT server with leaner Undertow server and enabling it to use Netty's native epoll transport instead of the default Java NIO transport for better network performance

Browser Test Client (Easy Manual Testing)

For quick interactive testing without curl or Postman, open the provided HTML file: /caterpillar-truck-robot/testing-html-client-page/robot-sim-client-v2.html (open it in a browser) This is a simple, self-contained HTML + JavaScript page that acts as a console for the entire API.

How to use it:

  1. Start the server (any profile)
  2. Open robot-sim-client-v2.html in Chrome/Firefox/Edge
  3. Set your base URL if not on localhost:8081
  4. Click buttons to fire requests — watch the response pane at the bottom

I used it for manual server-side debugging.