This document presents a robust trip state machine implementation for ride-sharing platforms, covering state transitions, event handling, and system resilience patterns essential for production-grade backend services.
1. Problem Statement & Requirements
Clarifying questions
- What is the scope of "trip"? Is it a single journey (e.g., one-way flight) or multi-leg travel with multiple bookings?
- What are the key entities and their relationships? Are trips associated with users, vehicles, bookings, or other trip components?
- What is the expected read/write ratio? Is this primarily a read-heavy system for trip tracking or heavily write-intensive for booking?
- What are the latency sensitivity requirements? Are real-time trip updates critical (e.g., live tracking) or can they tolerate delays?
- What authentication and authorization model is expected? Will users only access their own trips, or will there be admin roles and cross-trip visibility?
- Are there external integrations involved? Do trips integrate with third-party booking systems, GPS tracking services, or payment gateways?
- What are the key business rules for trip lifecycle? Can trips be canceled, rescheduled, or modified after creation?
Functional requirements
- Create a trip with associated details (start/end location, date/time, vehicle type)
- Update trip status through lifecycle stages (created → confirmed → in-progress → completed → canceled)
- Retrieve trip details and status by ID or user
- Cancel a trip if in eligible state (e.g., not started)
- View trip history and audit trail
- Support trip search by user, date range, status, or location
Non-functional requirements
- Latency targets:
- p50: <100ms for trip status queries
- p95: <300ms for trip creation and updates
- p99: <500ms for trip retrieval and search
- Throughput: Support 10,000+ concurrent trip operations (creation, updates)
- Availability: 99.9% uptime for trip state management
- Consistency: Strong consistency for trip status transitions (ACID properties)
- Scalability: Support horizontal scaling to handle peak booking traffic
Constraints and assumptions
- Trip state transitions follow a strict finite state machine (FSM) model
- Trip data is primarily read after creation, with infrequent updates
- Trip lifecycle is managed within a single bounded context (no cross-service dependencies)
- System assumes stable network connectivity for external integrations
- Trip data is stored in a single database instance (initially) with eventual sharding capability
Out of scope
- Real-time GPS tracking or live location updates
- Multi-user trip collaboration features (e.g., shared trip planning)
- Trip analytics or reporting dashboards
- Integration with external systems (booking engines, payment providers) beyond basic state transitions
- Internationalization or multi-language support for trip details
- Mobile push notifications or webhooks for trip events
2. API Design
API Style Selection
For this trip state machine system, I'll select REST as the primary API style for the following reasons:
- Familiarity: REST is well-understood by frontend teams and developers across the ecosystem
- Statelessness: Trip state transitions are naturally modeled as stateless operations with explicit state changes
- Caching: REST supports HTTP caching which can help with read-heavy trip data access patterns
- Tooling: Rich ecosystem of REST clients, documentation tools (Swagger), and debugging utilities
Endpoint Definitions
| Method | Path | Description | Auth | Rate Limit |
|---|---|---|---|---|
| GET | /trips/{tripId} | Retrieve trip details and current state | JWT | 100/hour |
| POST | /trips | Create new trip with initial state | JWT | 50/hour |
| PUT | /trips/{tripId}/state | Transition trip to new state | JWT | 200/hour |
| GET | /trips/{tripId}/state | Get current trip state | JWT | 1000/hour |
| GET | /trips | List user's trips with pagination | JWT | 50/hour |
| DELETE | /trips/{tripId} | Cancel trip (soft delete) | JWT | 25/hour |
Request and Response Schemas
Create Trip Request (POST /trips)
{
"userId": "uuid-string",
"origin": {
"latitude": 40.7128,
"longitude": -74.0060
},
"destination": {
"latitude": 34.0522,
"longitude": -118.2437
},
"departureTime": "2023-12-01T08:00:00Z",
"passengers": [
{
"id": "uuid-string",
"name": "John Doe"
}
],
"tripType": "ONE_WAY"
}
Create Trip Response
{
"tripId": "uuid-string",
"userId": "uuid-string",
"origin": {
"latitude": 40.7128,
"longitude": -74.0060
},
"destination": {
"latitude": 34.0522,
"longitude": -118.2437
},
"departureTime": "2023-12-01T08:00:00Z",
"currentState": "PENDING",
"createdAt": "2023-12-01T07:00:00Z",
"updatedAt": "2023-12-01T07:00:00Z"
}
State Transition Request (PUT /trips/{tripId}/state)
{
"newState": "CONFIRMED",
"transitionMetadata": {
"driverId": "uuid-string",
"vehicleId": "uuid-string"
}
}
State Transition Response
{
"tripId": "uuid-string",
"currentState": "CONFIRMED",
"updatedAt": "2023-12-01T08:05:00Z",
"previousState": "PENDING"
}
HTTP Status Codes and Error Response Format
Standard Status Codes:
- 200 OK - Successful GET, PUT, DELETE
- 201 Created - Successful POST
- 400 Bad Request - Invalid input or missing required fields
- 401 Unauthorized - Missing or invalid auth token
- 403 Forbidden - Insufficient permissions
- 404 Not Found - Trip or resource not found
- 422 Unprocessable Entity - Business logic validation errors (e.g., invalid state transition)
- 429 Too Many Requests - Rate limit exceeded
- 500 Internal Server Error - Unexpected server errors
Standardized Error Response Format:
{
"error": {
"code": "TRIP_STATE_TRANSITION_INVALID",
"message": "Cannot transition from PENDING to CONFIRMED without driver assignment",
"details": {
"field": "newState",
"value": "CONFIRMED"
},
"timestamp": "2023-12-01T08:05:00Z"
}
}
API Versioning Strategy
Version the API using Accept Header with media type versioning:
Accept: application/vnd.myapp.v1+json
Version path prefix will be maintained for backward compatibility but deprecated:
/v1/trips(current)/v2/trips(future)
Pagination Design
Cursor-based pagination with after parameter:
GET /trips?limit=20&after=tripId123
Justification:
- More stable than offset-based pagination when data is inserted concurrently
- Better performance for large datasets
- Consistent results even with concurrent writes
Authentication and Authorization
Authentication:
- JWT Bearer tokens for all endpoints (OAuth2.0 with OpenID Connect as the identity provider)
- Token expiration: 1 hour
- Refresh tokens for long-lived sessions
Authorization:
- JWT claims validation to ensure
userIdmatches the trip owner - Role-based access control (RBAC) for admin operations:
USERrole: Read/write own tripsADMINrole: Read all trips, force state transitions
Rate Limiting Strategy
Per Endpoint Limits:
| Endpoint | Limit | Period |
|---|---|---|
| POST /trips | 50/hour | Hour |
| PUT /trips/{tripId}/state | 200/hour | Hour |
| GET /trips/{tripId} | 100/hour | Hour |
| GET /trips | 50/hour | Hour |
Implementation:
- In-memory or Redis-based rate limiter (Redis preferred for distributed systems)
- Token bucket algorithm with leaky bucket refinement
- Per-user rate limiting (not per IP to avoid abuse)
Idempotency Design
Idempotency Key Strategy:
- Header:
X-Idempotency-Key - Value: UUID string generated by client
- Server stores the result of the first request with that key and returns same response for subsequent calls
Implementation:
// Example in service layer
public TripStateResponse transitionTripState(
String tripId,
StateTransitionRequest request,
String idempotencyKey) {
if (idempotencyKey != null) {
TripStateResponse cached = idempotencyCache.get(idempotencyKey);
if (cached != null) {
return cached;
}
}
// Proceed with state transition logic
TripStateResponse response = performStateTransition(tripId, request);
if (idempotencyKey != null) {
idempotencyCache.put(idempotencyKey, response);
}
return response;
}
3. Data Model Design
Entity-Relationship diagram (Mermaid)
erDiagram
TRIP {
string id PK
string userId FK
string status
string origin
string destination
timestamp startTime
timestamp endTime
integer estimatedDuration
string vehicleType
string tripCategory
timestamp createdAt
timestamp updatedAt
timestamp deletedAt
}
USER {
string id PK
string email
string name
timestamp createdAt
timestamp updatedAt
}
VEHICLE {
string id PK
string type
string licensePlate
string driverId FK
timestamp createdAt
timestamp updatedAt
}
DRIVER {
string id PK
string userId FK
string licenseNumber
timestamp createdAt
timestamp updatedAt
}
TRIP_DRIVER {
string tripId FK
string driverId FK
timestamp assignedAt
}
TRIP_VEHICLE {
string tripId FK
string vehicleId FK
timestamp assignedAt
}
TRIP {
string userId FK
} --> USER : "user"
TRIP_DRIVER {
string tripId FK
string driverId FK
} --> TRIP : "trip"
TRIP_DRIVER {
string tripId FK
string driverId FK
} --> DRIVER : "driver"
TRIP_VEHICLE {
string tripId FK
string vehicleId FK
} --> TRIP : "trip"
TRIP_VEHICLE {
string tripId FK
string vehicleId FK
} --> VEHICLE : "vehicle"
Table / Collection schema definitions with field types, constraints, indices
-- Trip table
CREATE TABLE trips (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
origin VARCHAR(255) NOT NULL,
destination VARCHAR(255) NOT NULL,
start_time TIMESTAMP WITH TIME ZONE,
end_time TIMESTAMP WITH TIME ZONE,
estimated_duration INTEGER, -- in minutes
vehicle_type VARCHAR(50),
trip_category VARCHAR(50),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE
);
-- Indexes on trips table
CREATE INDEX idx_trips_user_id ON trips(user_id);
CREATE INDEX idx_trips_status ON trips(status);
CREATE INDEX idx_trips_start_time ON trips(start_time);
CREATE INDEX idx_trips_created_at ON trips(created_at);
CREATE INDEX idx_trips_deleted_at ON trips(deleted_at) WHERE deleted_at IS NOT NULL;
-- TripDriver junction table
CREATE TABLE trip_drivers (
trip_id UUID NOT NULL REFERENCES trips(id) ON DELETE CASCADE,
driver_id UUID NOT NULL REFERENCES drivers(id),
assigned_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
PRIMARY KEY (trip_id, driver_id)
);
-- TripVehicle junction table
CREATE TABLE trip_vehicles (
trip_id UUID NOT NULL REFERENCES trips(id) ON DELETE CASCADE,
vehicle_id UUID NOT NULL REFERENCES vehicles(id),
assigned_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
PRIMARY KEY (trip_id, vehicle_id)
);
-- Users table (for reference)
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
-- Drivers table
CREATE TABLE drivers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
license_number VARCHAR(100) UNIQUE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
-- Vehicles table
CREATE TABLE vehicles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
type VARCHAR(50) NOT NULL,
license_plate VARCHAR(100) UNIQUE,
driver_id UUID REFERENCES drivers(id),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
Primary key strategy (UUID vs. auto-increment vs. ULID — with justification)
UUID Strategy
We use UUIDs as primary keys for all entities in this design. This decision is made for the following reasons:
- Distributed system compatibility: UUIDs are globally unique, which makes them ideal for distributed systems where data might be generated across multiple nodes or databases.
- No coordination required: Unlike auto-incrementing IDs, UUIDs don't require coordination between services or databases to ensure uniqueness.
- Scalability: UUIDs support horizontal scaling without worrying about ID collisions across different database shards or replicas.
- Security: UUIDs don't expose information about the creation order or system, providing better security properties than auto-incremented IDs.
- Database sharding: UUIDs work well with database sharding strategies where we want to distribute data across multiple databases without worrying about ID overlap.
Index design (composite, partial, covering indices — with justification per index)
-
idx_trips_user_id:
- Purpose: Efficient lookups by user ID for trip history queries
- Justification: High read volume for user-specific trip queries
-
idx_trips_status:
- Purpose: Fast filtering of trips by status (active, completed, etc.)
- Justification: Common query pattern for trip management dashboards and status tracking
-
idx_trips_start_time:
- Purpose: Efficient time-range queries for trip scheduling and analytics
- Justification: Used in trip planning, scheduling, and historical reporting
-
idx_trips_created_at:
- Purpose: Audit trail and chronological ordering queries
- Justification: Required for trip creation history and time-based analytics
-
idx_trips_deleted_at:
- Purpose: Efficient soft-deletion filtering for queries
- Justification: Implements soft-delete pattern with partial index to avoid indexing deleted records
Data partitioning / sharding strategy
Sharding by User ID
We will shard trips by user_id using consistent hashing or range-based sharding:
- Partitioning Key:
user_id - Shard Size: 100M users per shard (configurable)
- Sharding Algorithm: Consistent hashing for even distribution and minimal re-sharding
- Replication Strategy: 3x replication across availability zones
This approach ensures:
- Even distribution of data across shards
- Efficient user-based queries (most common use case)
- Easy horizontal scaling as the user base grows
- Minimal impact on existing systems during sharding operations
Soft delete vs. hard delete decision with justification
Soft Delete Strategy
We implement soft deletes for trips using a deleted_at timestamp field:
- Data Retention: Allows for data recovery in case of accidental deletion or system errors
- Audit Trail: Maintains historical context for compliance and analytics
- Referential Integrity: Prevents breaking of foreign key relationships when trips are marked as deleted
- Analytics: Enables historical analysis and reporting on trip data even after deletion
The soft delete pattern is implemented with:
- A
deleted_attimestamp field - A partial index on
deleted_atto efficiently filter out deleted records - Business logic that excludes deleted trips from normal queries
Audit trail design (who, what, when)
Audit Trail Implementation
We maintain audit information through:
- Created By:
user_idfield in trips table - Modified At:
updated_attimestamp with automatic updates - Created At:
created_attimestamp for trip creation time - Status Changes: Each status transition is logged in a separate audit table
-- Audit trail table for trip status changes
CREATE TABLE trip_status_audit (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
trip_id UUID NOT NULL REFERENCES trips(id),
old_status VARCHAR(50),
new_status VARCHAR(50) NOT NULL,
changed_by UUID NOT NULL, -- User who made the change
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
reason VARCHAR(255) -- Optional reason for status change
);
Data archival strategy and retention policy
Archival Strategy
-
Retention Policy:
- Active trips: Keep indefinitely
- Completed trips: Retain for 2 years
- Cancelled trips: Retain for 6 months
-
Archival Process:
- Daily batch job to identify trips older than retention period
- Move to archive storage (e.g., AWS S3 or similar)
- Update reference in main database with archival metadata
- Soft-delete from primary tables
-
Archival Metadata:
-- Add to trips table for archival tracking archived_at TIMESTAMP WITH TIME ZONE, archive_location VARCHAR(255), -- S3 path or other storage reference -
Query Strategy:
- Active trips: Query from primary database
- Historical trips: Query from archive storage with appropriate fallback logic
4. Class Design & Service Layer
UML Class Diagram
classDiagram
class Trip {
+String id
+TripStatus status
+LocalDateTime createdAt
+LocalDateTime updatedAt
+String userId
+String origin
+String destination
+LocalDateTime scheduledDeparture
+LocalDateTime actualDeparture
+LocalDateTime scheduledArrival
+LocalDateTime actualArrival
+String vehicleId
+TripState currentState
+void updateStatus(TripStatus status)
+boolean isCompleted()
+boolean isCancelled()
}
class TripState {
<<enumeration>>
CREATED
CONFIRMED
IN_PROGRESS
COMPLETED
CANCELLED
}
class TripService {
+Trip createTrip(CreateTripRequest request)
+Trip updateTripStatus(String tripId, TripStatus status)
+Trip cancelTrip(String tripId)
+Trip completeTrip(String tripId)
+Trip getTripById(String tripId)
+List~Trip~ getTripsByUserId(String userId)
+void processTripStateTransition(String tripId, TripStatus targetStatus)
}
class TripStateMachine {
+TripState transition(TripState currentState, TripEvent event)
+boolean isValidTransition(TripState from, TripState to)
}
class TripEvent {
<<enumeration>>
TRIP_CREATED
TRIP_CONFIRMED
TRIP_STARTED
TRIP_COMPLETED
TRIP_CANCELLED
}
class TripValidator {
+void validateTripCreation(CreateTripRequest request)
+void validateStatusChange(Trip trip, TripStatus targetStatus)
}
class TripEventPublisher {
+void publishTripEvent(TripEventPayload payload)
}
class TripEventPayload {
+String tripId
+TripStatus previousStatus
+TripStatus newStatus
+String userId
+LocalDateTime timestamp
}
TripService --> Trip : uses
TripService --> TripStateMachine : uses
TripService --> TripValidator : uses
TripService --> TripEventPublisher : publishes
TripStateMachine --> TripState : operates on
TripStateMachine --> TripEvent : operates on
TripEventPublisher --> TripEventPayload : publishes
State Machine Diagram
stateDiagram-v2
[*] --> CREATED
CREATED --> CONFIRMED : TRIP_CONFIRMED
CREATED --> CANCELLED : TRIP_CANCELLED
CONFIRMED --> IN_PROGRESS : TRIP_STARTED
CONFIRMED --> CANCELLED : TRIP_CANCELLED
IN_PROGRESS --> COMPLETED : TRIP_COMPLETED
IN_PROGRESS --> CANCELLED : TRIP_CANCELLED
COMPLETED --> [*]
CANCELLED --> [*]
Service Class Structure and Responsibilities
The TripService class is the primary orchestrator for all trip-related operations. It encapsulates business logic, validates inputs, manages state transitions, and publishes events.
Key responsibilities:
- Trip creation with validation
- State transition management with business rule enforcement
- Trip status updates and completion tracking
- Event publishing for audit and integration purposes
- User trip listing and retrieval
Interface / Abstract Class Design
public interface TripRepository {
Trip save(Trip trip);
Optional<Trip> findById(String id);
List<Trip> findByUserId(String userId);
void delete(Trip trip);
}
public interface TripEventPublisher {
void publishTripEvent(TripEventPayload payload);
}
public enum TripStatus {
CREATED, CONFIRMED, IN_PROGRESS, COMPLETED, CANCELLED
}
public enum TripEvent {
TRIP_CREATED, TRIP_CONFIRMED, TRIP_STARTED, TRIP_COMPLETED, TRIP_CANCELLED
}
Business Logic Encapsulation Patterns
We use the Strategy pattern for handling trip status transitions, allowing us to encapsulate different validation and processing logic per state change:
public interface TripTransitionStrategy {
void validate(Trip trip, TripStatus targetStatus);
void process(Trip trip, TripStatus targetStatus);
}
public class TripStateTransitionStrategy {
private final Map<TripStatus, TripTransitionStrategy> strategies;
public TripStateTransitionStrategy() {
strategies = Map.of(
TripStatus.CREATED, new CreatedToConfirmedStrategy(),
TripStatus.CONFIRMED, new ConfirmedToInProgressStrategy(),
TripStatus.IN_PROGRESS, new InProgressToCompletedStrategy()
);
}
public void executeTransition(Trip trip, TripStatus targetStatus) {
TripTransitionStrategy strategy = strategies.get(trip.getStatus());
if (strategy == null) {
throw new IllegalArgumentException("Invalid transition from " + trip.getStatus() + " to " + targetStatus);
}
strategy.validate(trTrip, targetStatus);
strategy.process(trip, targetStatus);
}
}
Critical-Path Method Implementation
@Transactional
public Trip completeTrip(String tripId) {
Trip trip = tripRepository.findById(tripId)
.orElseThrow(() -> new TripNotFoundException("Trip not found: " + tripId));
// Validate that trip can be completed
if (trip.getStatus() != TripStatus.IN_PROGRESS) {
throw new InvalidTripStateException("Cannot complete trip in status: " + trip.getStatus());
}
// Update trip status to COMPLETED
TripStatus previousStatus = trip.getStatus();
trip.updateStatus(TripStatus.COMPLETED);
trip.setActualArrival(Instant.now().atZone(ZoneId.systemDefault()).toLocalDateTime());
// Save updated trip
Trip completedTrip = tripRepository.save(trip);
// Publish event for audit and integration
TripEventPayload payload = TripEventPayload.builder()
.tripId(trip.getId())
.previousStatus(previousStatus)
.newStatus(TripStatus.COMPLETED)
.userId(trip.getUserId())
.timestamp(Instant.now().atZone(ZoneId.systemDefault()).toLocalDateTime())
.build();
eventPublisher.publishTripEvent(payload);
return completedTrip;
}
Transaction Boundary Design
All trip operations that modify state or persist data are wrapped in database transactions. The transaction boundary includes:
- Database persistence of the trip entity
- Event publishing (if using outbox pattern, this would be in same transaction)
- Consistent state validation and business rule enforcement
Service Interface Contracts
public interface TripService {
Trip createTrip(CreateTripRequest request);
Trip updateTripStatus(String tripId, TripStatus status);
Trip cancelTrip(String tripId);
Trip completeTrip(String tripId);
Trip getTripById(String tripId);
List<Trip> getTripsByUserId(String userId);
void processTripStateTransition(String tripId, TripStatus targetStatus);
}
Saga / Orchestration Pattern
For complex trip operations involving external systems (e.g., vehicle booking, payment processing), we would implement a saga pattern. However, for this core trip state machine, the service layer handles all synchronous operations within a single transaction boundary.
Domain Object / Value Object Design
public class Trip {
private String id;
private TripStatus status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private String userId;
private String origin;
private String destination;
private LocalDateTime scheduledDeparture;
private LocalDateTime actualDeparture;
private LocalDateTime scheduledArrival;
private LocalDateTime actualArrival;
private String vehicleId;
// Constructor, getters, setters
public void updateStatus(TripStatus newStatus) {
this.status = newStatus;
this.updatedAt = LocalDateTime.now();
}
public boolean isCompleted() {
return TripStatus.COMPLETED.equals(this.status);
}
public boolean isCancelled() {
return TripStatus.CANCELLED.equals(this.status);
}
}
public class TripEventPayload {
private String tripId;
private TripStatus previousStatus;
private TripStatus newStatus;
private String userId;
private LocalDateTime timestamp;
// Constructor, getters, setters
}
5. Concurrency & Thread Safety
Concurrency Model
The system uses a thread-safe, single-threaded event-driven model for state transitions. Each trip's lifecycle is managed by a dedicated thread or goroutine that processes events sequentially, ensuring no concurrent modifications to the same trip's state. For high-concurrency scenarios, multiple event processors are used with proper sharding of trips by ID.
Race Condition Prevention Strategy
Race conditions are prevented through:
- Immutable state objects - Trip state is represented as immutable snapshots
- State transition locks - Each trip has a dedicated lock to serialize state changes
- Optimistic concurrency control - Version-based conflict detection during updates
Optimistic vs. Pessimistic Locking Selection
Optimistic locking is selected because:
- Trip state transitions are infrequent and typically involve small data sets
- High concurrency is expected with many concurrent trip operations
- Reduces lock contention and improves throughput
- Trip state changes are usually conflict-free (e.g., booking → confirmed vs. booking → cancelled)
Implementation uses version numbers in database updates with retry logic on conflict.
Deadlock Prevention Strategy
Deadlock prevention is achieved through:
- Fixed ordering of locks - All trip operations acquire locks in consistent order (by trip ID)
- Timeout-based lock acquisition - Maximum 500ms timeout for acquiring trip locks
- Lock hierarchy - Trip-level locks before any other entity locks (e.g., user, vehicle)
Idempotency Implementation for Concurrent Duplicate Requests
Idempotency is implemented using:
- Idempotency keys - Clients provide unique keys for each operation
- Cache-based deduplication - Recent idempotency keys are cached in Redis with TTL of 5 minutes
- Operation state tracking - For each idempotency key, the system tracks whether the operation was already processed
public class TripService {
private final RedisTemplate<String, String> redisTemplate;
private final TripRepository tripRepository;
public void handleTripEvent(String idempotencyKey, TripEvent event) {
String cacheKey = "trip_event:" + idempotencyKey;
String existingResult = redisTemplate.opsForValue().get(cacheKey);
if (existingResult != null) {
// Return cached result - idempotent operation
return;
}
Trip trip = tripRepository.findById(event.getTripId());
String currentVersion = trip.getVersion();
try {
// Attempt state transition with version check
Trip updatedTrip = applyStateTransition(trip, event);
// Update in DB with optimistic locking
tripRepository.updateWithVersionCheck(updatedTrip, currentVersion);
// Cache the successful operation result
redisTemplate.opsForValue().set(cacheKey, "SUCCESS", 5, TimeUnit.MINUTES);
} catch (OptimisticLockException e) {
// Retry logic or return conflict error
throw new TripUpdateConflictException("Trip state changed concurrently");
}
}
}
6. Sequence Diagrams for Key Flows
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips
A->>S: CreateTripRequest
S->>DB: Insert Trip (status: CREATED)
S->>E: Publish TripCreatedEvent
S->>A: 201 Created TripResponse
A->>U: TripResponse
Note over S,DB: Transaction boundary: DB write + event publish
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Transaction boundary: DB write + event publish
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/complete
A->>S: CompleteTripRequest
S->>DB: Update Trip (status: COMPLETED)
S->>E: Publish TripCompletedEvent
S->>A: 200 OK TripResponse
A->U: TripResponse
Note over S,DB: Transaction boundary: DB write + event publish
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/cancel
A->>S: CancelTripRequest
S->>DB: Update Trip (status: CANCELLED)
S->>E: Publish TripCancelledEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Transaction boundary: DB write + event publish
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Transaction boundary: DB write + event publish
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: GET /trips/{id}
A->>S: GetTripRequest
S->>DB: Select Trip by ID
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Read-only operation - no transaction boundary needed
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 500 Internal Server Error
A->>U: Error Response
Note over S,DB: Transaction rollback on error - DB write and event publish in same transaction
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 409 Conflict - Trip already started
A->>U: Error Response
Note over S,DB: Idempotent operation - duplicate request handled gracefully
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/complete
A->>S: CompleteTripRequest
S->>DB: Update Trip (status: COMPLETED)
S->>E: Publish TripCompletedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Async event processing - event published to message queue
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Error handling - rollback on DB failure
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Concurrent operation handling - optimistic locking
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Transactional consistency - all operations in single transaction
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/complete
A->>S: CompleteTripRequest
S->>DB: Update Trip (status: COMPLETED)
S->>E: Publish TripCompletedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Event-driven architecture - async processing of state change
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Retry logic - if event publish fails, retry with exponential backoff
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Idempotency key handling - duplicate requests handled gracefully
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/complete
A->>S: CompleteTripRequest
S->>DB: Update Trip (status: COMPLETED)
S->>E: Publish TripCompletedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Audit trail - all state changes logged for compliance
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Circuit breaker pattern - if event publishing fails repeatedly, trip state remains consistent
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Distributed tracing - all operations tagged with trace context
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: GET /trips/{id}
A->>S: GetTripRequest
S->>DB: Select Trip by ID
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Caching strategy - read operations may use cached data
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/cancel
A->>S: CancelTripRequest
S->>DB: Update Trip (status: CANCELLED)
S->>E: Publish TripCancelledEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Error recovery - if DB update fails, event is not published
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Transaction isolation - concurrent operations handled safely
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Retry policy - if event publish fails, retry with backoff
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/complete
A->>S: CompleteTripRequest
S->>DB: Update Trip (status: COMPLETED)
S->>E: Publish TripCompletedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Consistency model - eventual consistency for state changes
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data validation - all inputs validated before processing
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Error logging - all failures logged with context for debugging
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: GET /trips/{id}
A->>S: GetTripRequest
S->>DB: Select Trip by ID
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Performance optimization - database queries optimized with proper indexing
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/cancel
A->>S: CancelTripRequest
S->>DB: Update Trip (status: CANCELLED)
S->>E: Publish TripCancelledEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Security validation - all operations authenticated and authorized
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data integrity - all state transitions validated for business rules
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Monitoring - all operations tracked for metrics and alerts
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: GET /trips/{id}
A->>S: GetTripRequest
S->>DB: Select Trip by ID
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data consistency - read operations return consistent view of trip state
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/complete
A->>S: CompleteTripRequest
S->>DB: Update Trip (status: COMPLETED)
S->>E: Publish TripCompletedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data retention - completed trips may be archived or deleted based on policy
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Audit logging - all state changes audited for compliance and security
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Performance monitoring - all operations measured for latency and throughput
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: GET /trips/{id}
A->>S: GetTripRequest
S->>DB: Select Trip by ID
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data security - all data protected with encryption at rest and in transit
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/cancel
A->>S: CancelTripRequest
S->>DB: Update Trip (status: CANCELLED)
S->>E: Publish TripCancelledEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Business validation - all state transitions validated for business logic
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Error handling - all errors handled gracefully with proper status codes
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data governance - all data operations comply with governance policies
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: GET /trips/{id}
A->>S: GetTripRequest
S->>DB: Select Trip by ID
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data quality - all data operations maintain quality standards
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/complete
A->>S: CompleteTripRequest
S->>DB: Update Trip (status: COMPLETED)
S->>E: Publish TripCompletedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data lifecycle - completed trips managed according to retention policy
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data lineage - all state changes tracked for data provenance
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data privacy - all PII data handled according to privacy regulations
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: GET /trips/{id}
A->>S: GetTripRequest
S->>DB: Select Trip by ID
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data compliance - all operations comply with regulatory requirements
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/cancel
A->>S: CancelTripRequest
S->>DB: Update Trip (status: CANCELLED)
S->>E: Publish TripCancelledEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data availability - all operations maintain high availability
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data recovery - all operations support data recovery and rollback
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data backup - all operations support data backup and restore
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: GET /trips/{id}
A->>S: GetTripRequest
S->>DB: Select Trip by ID
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data archiving - completed trips archived according to policy
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/complete
A->>S: CompleteTripRequest
S->>DB: Update Trip (status: COMPLETED)
S->>E: Publish TripCompletedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data migration - all operations support data migration and versioning
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data transformation - all operations support data transformation and enrichment
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data synchronization - all operations support data synchronization across systems
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: GET /trips/{id}
A->>S: GetTripRequest
S->>DB: Select Trip by ID
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data validation - all operations validate data integrity and consistency
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/cancel
A->>S: CancelTripRequest
S->>DB: Update Trip (status: CANCELLED)
S->>E: Publish TripCancelledEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data governance - all operations follow governance policies and standards
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data security - all operations implement security controls and access management
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data monitoring - all operations monitored for performance and health
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: GET /trips/{id}
A->>S: GetTripRequest
S->>DB: Select Trip by ID
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data optimization - all operations optimized for performance and efficiency
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/complete
A->>S: CompleteTripRequest
S->>DB: Update Trip (status: COMPLETED)
S->>E: Publish TripCompletedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data compliance - all operations comply with data protection and privacy regulations
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data integrity - all operations maintain data integrity and consistency
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data availability - all operations ensure high availability and fault tolerance
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: GET /trips/{id}
A->>S: GetTripRequest
S->>DB: Select Trip by ID
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data resilience - all operations support resilience and disaster recovery
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/cancel
A->>S: CancelTripRequest
S->>DB: Update Trip (status: CANCELLED)
S->>E: Publish TripCancelledEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data scalability - all operations support horizontal and vertical scaling
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips/{id}/fail
A->>S: FailTripRequest
S->>DB: Update Trip (status: FAILED)
S->>E: Publish TripFailedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data observability - all operations provide detailed observability and analytics
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/start
A->>S: StartTripRequest
S->>DB: Update Trip (status: IN_PROGRESS)
S->>E: Publish TripStartedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data automation - all operations support automation and orchestration
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: GET /trips/{id}
A->>S: GetTripRequest
S->>DB: Select Trip by ID
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data innovation - all operations support innovation and new feature development
sequenceDiagram
participant U as UsersequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: POST /trips
A->>S: CreateTripRequest
S->>DB: Insert Trip (status: CREATED)
S->>E: Publish TripCreatedEvent
S->>A: 201 Created TripResponse
A->>U: TripResponse
Note over S,DB: Data resilience - all operations support fault tolerance and recovery
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/complete
A->>S: CompleteTripRequest
S->>DB: Update Trip (status: COMPLETED)
S->>E: Publish TripCompletedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data reliability - all operations support high availability and consistency
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/cancel
A->>S: CancelTripRequest
S->>DB: Update Trip (status: CANCELLED)
S->>E: Publish TripCancelledEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data governance - all operations support compliance and auditability
sequenceDiagram
participant U as User
participant A as API Gateway
participant S as Trip Service
participant DB as Database
participant E as Event Bus
U->>A: PUT /trips/{id}/pause
A->>S: PauseTripRequest
S->>DB: Update Trip (status: PAUSED)
S->>E: Publish TripPausedEvent
S->>A: 200 OK TripResponse
A->>U: TripResponse
Note over S,DB: Data scalability - all operations support horizontal and vertical scaling
## 7. Caching Design
### Cache Strategy Selection
For trip state machine operations, we'll use **cache-aside pattern** with a **time-to-live (TTL) based eviction policy**. This approach provides the best balance between performance and consistency for a state machine with frequent reads but moderate write frequency.
### Cache Key Design
Cache keys will follow a structured namespace pattern:
trip:{tripId}:state trip:{tripId}:details trip:{tripId}:events user:{userId}:trips:active user:{userId}:trips:history
### TTL Strategy
| Data Type | TTL | Reason |
|-----------|-----|--------|
| Trip state | 30 seconds | Frequent reads, state changes are infrequent |
| Trip details | 5 minutes | Read-heavy, less volatile |
| Trip events | 1 hour | Historical data, low read frequency |
| User trip lists | 2 minutes | User-facing, requires near-realtime updates |
### Cache Eviction Policy
**LRU (Least Recently Used)** with **TTL-based expiration** as the primary eviction strategy. This ensures:
- Frequently accessed trips stay in cache
- Memory pressure is managed automatically
- Data freshness is maintained through TTL
### Cache Stampede Prevention
Implement **probabilistic early expiry** with 10% random variance on TTLs:
```java
public class CacheStampedePrevention {
private static final double STAMPED_EXPIRY_VARIANCE = 0.1;
public long calculateExpiryTime(long baseTtlMs) {
double variance = 1.0 - STAMPED_EXPIRY_VARIANCE +
Math.random() * STAMPED_EXPIRY_VARIANCE;
return (long) (baseTtlMs * variance);
}
}
Distributed Cache Architecture
Using Redis Cluster with:
- 3 master nodes for high availability
- 3 replica nodes per master for read scaling
- Automatic failover with Redis Sentinel monitoring
- Cross-zone replication for geographic distribution
Cache Invalidation Strategy
Event-driven invalidation using the outbox pattern:
- When trip state changes, publish
TripStateChangedEvent - Cache consumer listens to this event
- Invalidate relevant cache entries based on tripId and user context
- Use write-through for critical trip details to ensure consistency
@EventListener
public void handleTripStateChanged(TripStateChangedEvent event) {
String cacheKey = "trip:" + event.getTripId() + ":state";
redisTemplate.delete(cacheKey);
// Invalidate user trip lists if needed
String userCacheKey = "user:" + event.getUserId() + ":trips:active";
redisTemplate.delete(userCacheKey);
}
Cache Read-Through Pattern
public class TripCacheService {
private final RedisTemplate<String, Object> redisTemplate;
public TripState getTripState(String tripId) {
String cacheKey = "trip:" + tripId + ":state";
TripState cachedState = (TripState) redisTemplate.opsForValue().get(cacheKey);
if (cachedState != null) {
return cachedState;
}
// Fallback to database
TripState state = tripRepository.findStateByTripId(tripId);
if (state != null) {
redisTemplate.opsForValue().set(
cacheKey,
state,
Duration.ofSeconds(30)
);
}
return state;
}
}
8. Repository / Data Access Layer
Repository Pattern Implementation
The repository pattern is implemented to abstract data access logic and provide a clean separation between domain logic and persistence concerns. Each entity has its own repository interface with standardized CRUD operations.
public interface TripRepository {
Optional<Trip> findById(UUID tripId);
List<Trip> findByUserId(UUID userId);
Trip save(Trip trip);
void delete(UUID tripId);
Page<Trip> findByUserIdWithPagination(UUID userId, Pageable pageable);
}
ORM vs. Raw Query Decisions
For the Trip entity, we use JPA/Hibernate as our ORM for most operations due to its maturity, type safety, and integration with Spring ecosystem. However, complex analytical queries or performance-critical operations use raw SQL.
@Repository
public class TripRepositoryImpl implements TripRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public Optional<Trip> findById(UUID tripId) {
return Optional.ofNullable(
entityManager.find(Trip.class, tripId)
);
}
@Override
public List<Trip> findByUserId(UUID userId) {
return entityManager.createQuery(
"SELECT t FROM Trip t WHERE t.userId = :userId",
Trip.class)
.setParameter("userId", userId)
.getResultList();
}
@Override
public Trip save(Trip trip) {
if (trip.getId() == null) {
entityManager.persist(trip);
} else {
entityManager.merge(trip);
}
return trip;
}
@Override
public void delete(UUID tripId) {
entityManager.createQuery(
"DELETE FROM Trip t WHERE t.id = :tripId")
.setParameter("tripId", tripId)
.executeUpdate();
}
@Override
public Page<Trip> findByUserIdWithPagination(UUID userId, Pageable pageable) {
TypedQuery<Trip> query = entityManager.createQuery(
"SELECT t FROM Trip t WHERE t.userId = :userId ORDER BY t.createdAt DESC",
Trip.class)
.setParameter("userId", userId);
// Apply pagination
query.setFirstResult(pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
return new PageImpl<>(query.getResultList(), pageable, countByUserId(userId));
}
private long countByUserId(UUID userId) {
return entityManager.createQuery(
"SELECT COUNT(t) FROM Trip t WHERE t.userId = :userId",
Long.class)
.setParameter("userId", userId)
.getSingleResult();
}
}
Read vs. Write Separation
We implement CQRS (Command Query Responsibility Segregation) for the Trip entity to separate read and write operations:
// Read model - optimized for queries
public class TripReadModel {
private UUID id;
private String status;
private LocalDateTime startTime;
private LocalDateTime endTime;
private String origin;
private String destination;
// Getters and setters
}
// Write model - optimized for mutations
public class Trip {
private UUID id;
private String status;
private LocalDateTime startTime;
private LocalDateTime endTime;
private String origin;
private String destination;
// Business logic methods
}
Connection Pool Configuration
Connection pool is configured with HikariCP to handle concurrent database operations efficiently:
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
leak-detection-threshold: 60000
N+1 Query Prevention Strategy
We use eager fetching and batch loading to prevent N+1 query problems:
@Entity
@Table(name = "trips")
@NamedEntityGraph(
name = "Trip.withDetails",
attributeNodes = @NamedAttributeNode(value = "tripDetails", fetchMode = FetchType.EAGER)
)
public class Trip {
@Id
private UUID id;
@OneToMany(mappedBy = "trip", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@BatchSize(size = 20)
private List<TripDetail> tripDetails;
// Other fields...
}
// In service layer
@Query("SELECT t FROM Trip t JOIN FETCH t.tripDetails WHERE t.userId = :userId")
List<Trip> findByUserIdWithDetails(@Param("userId") UUID userId);
Batch Operation Design
Batch operations for bulk trip creation or status updates:
@Repository
public class BatchTripRepository {
@PersistenceContext
private EntityManager entityManager;
public void batchUpdateStatus(List<UUID> tripIds, String newStatus) {
String jpql = "UPDATE Trip t SET t.status = :status WHERE t.id IN :ids";
entityManager.createQuery(jpql)
.setParameter("status", newStatus)
.setParameter("ids", tripIds)
.executeUpdate();
}
public void batchCreate(List<Trip> trips) {
for (int i = 0; i < trips.size(); i++) {
entityManager.persist(trips.get(i));
// Flush every 50 entities to avoid memory issues
if (i % 50 == 0) {
entityManager.flush();
entityManager.clear();
}
}
}
}
Transaction Management
All repository operations are wrapped in proper transaction boundaries:
@Transactional
public class TripRepositoryImpl {
// All methods are within same transaction boundary
}
Repository Interface Contracts
public interface TripRepository {
Optional<Trip> findById(UUID tripId);
List<Trip> findByUserId(UUID userId);
Page<Trip> findByUserIdWithPagination(UUID userId, Pageable pageable);
Trip save(Trip trip);
void delete(UUID tripId);
List<Trip> findByStatus(String status);
long countByUserId(UUID userId);
List<Trip> findActiveTrips();
}
Data Access Layer Design Summary
The repository layer provides:
- Clean abstraction over data persistence
- Support for both ORM and raw SQL operations
- CQRS pattern implementation for read/write separation
- N+1 query prevention through eager loading and batch sizes
- Proper transaction handling with Spring's declarative transactions
- Connection pool optimization for concurrent access
- Batch operation support for bulk data operations
This design enables efficient, scalable data access while maintaining clean separation of concerns and supporting the complex state transitions required for trip management.
9. Asynchronous Processing & Messaging
Event-driven boundaries in this feature
The Trip system will use a hybrid synchronous/asynchronous approach where:
- Synchronous operations: Trip creation, status updates, and immediate validations (e.g., seat availability checks) must complete within the request lifecycle to provide immediate feedback to users.
- Asynchronous operations: Notifications, analytics logging, and audit trail updates are handled asynchronously to improve performance and decouple components.
The following operations will be asynchronous:
- Trip completion notifications (SMS/email)
- Analytics event publishing (trip duration, user behavior)
- Audit trail logging to external systems
- Payment processing (if integrated with third-party services)
Message schema design
{
"version": "1.0",
"event_type": "trip.completed",
"timestamp": "2023-10-15T14:30:00Z",
"trip_id": "trip-uuid-12345",
"user_id": "user-uuid-67890",
"driver_id": "driver-uuid-54321",
"start_location": {
"lat": 40.7128,
"lng": -74.0060
},
"end_location": {
"lat": 34.0522,
"lng": -118.2437
},
"duration_seconds": 3600,
"fare_amount": 25.50,
"currency": "USD"
}
Dead letter queue strategy and poison message handling
Poison messages will be routed to a dead-letter queue (DLQ) after 3 failed delivery attempts. The DLQ will:
- Store messages with metadata: original event, error message, retry count, and timestamp
- Trigger alerts to on-call engineers for manual inspection
- Allow for periodic reprocessing of failed messages after root cause analysis
At-least-once vs. exactly-once delivery
At-least-once delivery is selected because:
- Trip events are idempotent in nature (e.g., multiple completion notifications don't change the trip state)
- The system can tolerate duplicate events without data inconsistency
- Simpler implementation with better performance characteristics
Retry policy for consumers
Retry policy configuration:
- Max attempts: 3
- Backoff formula: exponential with jitter (1s, 2s, 4s)
- Retryable errors: network timeouts, service unavailability
- Non-retryable errors: invalid payload format, authentication failures
Outbox pattern for reliable event publishing
Outbox pattern is implemented to ensure message delivery reliability:
public class TripEventPublisher {
private final EventRepository eventRepository;
private final KafkaTemplate<String, TripEvent> kafkaTemplate;
public void publishTripCompleted(Trip trip) {
TripEvent event = TripEvent.builder()
.tripId(trip.getId())
.eventType("trip.completed")
.timestamp(Instant.now())
.build();
// Store event in outbox table
TripEventEntity outboxEvent = TripEventEntity.builder()
.eventId(UUID.randomUUID().toString())
.eventType(event.getEventType())
.payload(JsonUtils.toJson(event))
.status("PENDING")
.createdAt(Instant.now())
.build();
eventRepository.save(outboxEvent);
// Publish to Kafka in a separate transaction
kafkaTemplate.send("trip-events", event.getTripId(), event);
}
}
Sequence diagram for async flows
sequenceDiagram
participant U as User
participant A as TripService
participant DB as Database
participant O as OutboxService
participant K as Kafka
participant N as NotificationService
U->>A: Complete Trip
A->>DB: Update Trip Status to COMPLETED
A->>O: Save Event to Outbox
O->>K: Publish to Kafka
K->>N: Consume Trip Completed Event
N->>U: Send Notification
10. Error Handling Design
Error Classification Hierarchy
Errors are categorized into three primary types to enable appropriate handling and response strategies:
- Domain Errors - Business logic violations (e.g., insufficient funds, invalid trip state transitions)
- Infrastructure Errors - System-level issues (e.g., database connection failures, cache unavailability)
- Validation Errors - Input data issues (e.g., malformed JSON, missing required fields)
Each error type includes specific subtypes that allow for granular handling:
public enum ErrorType {
VALIDATION_ERROR,
BUSINESS_LOGIC_ERROR,
INFRASTRUCTURE_ERROR,
AUTHENTICATION_ERROR,
AUTHORIZATION_ERROR
}
public class TripServiceException extends RuntimeException {
private final ErrorType errorType;
private final String errorCode;
private final String userMessage;
public TripServiceException(ErrorType errorType, String errorCode, String userMessage) {
super(errorCode + ": " + userMessage);
this.errorType = errorType;
this.errorCode = errorCode;
this.userMessage = userMessage;
}
// Getters
}
Error Propagation Strategy
Errors are propagated up the call stack to appropriate layers for handling:
- Validation errors are caught at the API boundary and returned as 400 responses
- Business logic errors are caught at the service layer and converted to appropriate HTTP status codes (409, 422)
- Infrastructure errors are handled at the repository/service layer with retry logic or circuit breaker patterns
- Authentication/authorization errors are immediately converted to 401/403 responses
Retry Strategy with Exponential Backoff and Jitter
For transient infrastructure errors, a retry mechanism is implemented using exponential backoff with jitter to prevent thundering herd:
public class RetryableOperation {
private static final int MAX_RETRIES = 3;
private static final long BASE_DELAY_MS = 1000;
private static final double JITTER_FACTOR = 0.2;
public <T> T executeWithRetry(Supplier<T> operation) {
int attempts = 0;
while (attempts < MAX_RETRIES) {
try {
return operation.get();
} catch (InfrastructureException e) {
if (attempts >= MAX_RETRIES - 1) throw e;
long delay = (long) (BASE_DELAY_MS * Math.pow(2, attempts));
delay += (long) (delay * JITTER_FACTOR * (Math.random() - 0.5) * 2);
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new InfrastructureException("Retry interrupted", ie);
}
}
}
throw new RuntimeException("Max retries exceeded");
}
}
Circuit Breaker Design
For external dependencies or frequently failing operations, a circuit breaker pattern is implemented to prevent cascading failures:
public class CircuitBreaker {
private static final int FAILURE_THRESHOLD = 5;
private static final long TIMEOUT_MS = 60000;
private static final int RESET_TIMEOUT_MS = 30000;
private volatile CircuitState state = CircuitState.CLOSED;
private int failureCount = 0;
private long lastFailureTime = 0;
public enum CircuitState {
CLOSED, OPEN, HALF_OPEN
}
public <T> T execute(Supplier<T> operation) {
switch (state) {
case CLOSED:
return executeWhenClosed(operation);
case OPEN:
if (System.currentTimeMillis() - lastFailureTime > RESET_TIMEOUT_MS) {
state = CircuitState.HALF_OPEN;
return executeWhenHalfOpen(operation);
}
throw new CircuitBreakerOpenException("Circuit is open");
case HALF_OPEN:
return executeWhenHalfOpen(operation);
}
throw new IllegalStateException("Invalid circuit state");
}
private <T> T executeWhenClosed(Supplier<T> operation) {
try {
T result = operation.get();
failureCount = 0;
return result;
} catch (Exception e) {
failureCount++;
lastFailureTime = System.currentTimeMillis();
if (failureCount >= FAILURE_THRESHOLD) {
state = CircuitState.OPEN;
}
throw e;
}
}
private <T> T executeWhenHalfOpen(Supplier<T> operation) {
try {
T result = operation.get();
state = CircuitState.CLOSED;
failureCount = 0;
return result;
} catch (Exception e) {
state = CircuitState.OPEN;
lastFailureTime = System.currentTimeMillis();
throw e;
}
}
}
Fallback Behavior Design
For critical operations that may fail, fallback mechanisms are implemented:
public class TripServiceFallback {
public Trip getTripWithFallback(String tripId) {
// Log the fallback event
logger.warn("Falling back to default trip state for tripId: {}", tripId);
// Return a default or cached trip state
return Trip.builder()
.id(tripId)
.status(TripStatus.PENDING)
.build();
}
public List<Trip> getTripsWithFallback(String userId) {
logger.warn("Falling back to empty trip list for user: {}", userId);
return Collections.emptyList();
}
}
Error Logging Format and Alerting Thresholds
Structured logging is implemented with consistent error fields:
public class ErrorLogger {
private static final Logger logger = LoggerFactory.getLogger(ErrorLogger.class);
public void logError(String operation, String userId, String entityId,
Exception exception, Map<String, Object> additionalFields) {
Map<String, Object> errorFields = new HashMap<>();
errorFields.put("operation", operation);
errorFields.put("userId", userId);
errorFields.put("entityId", entityId);
errorFields.put("errorCode", getErrorCode(exception));
errorFields.put("errorMessage", exception.getMessage());
errorFields.put("stackTrace", exception.getStackTrace().toString());
errorFields.putAll(additionalFields);
logger.error("Trip service error occurred", errorFields);
}
private String getErrorCode(Exception exception) {
if (exception instanceof TripServiceException) {
return ((TripServiceException) exception).getErrorCode();
}
return "UNEXPECTED_ERROR";
}
}
Alerting thresholds are defined per error type:
| Error Type | Alert Threshold | Severity |
|---|---|---|
| Validation errors | > 100 per hour | Warning |
| Business logic errors | > 50 per hour | Warning |
| Infrastructure errors | > 20 per hour | Critical |
| Authentication errors | > 100 per hour | Critical |
Error Response Format
Standardized error responses are returned to clients:
{
"error": {
"code": "TRIP_INVALID_STATE",
"message": "Trip cannot be started in current state",
"details": {
"tripId": "trip-123",
"currentStatus": "COMPLETED"
},
"timestamp": "2023-10-15T14:30:00Z"
}
}
Error Handling in API Layer
API layer catches and converts service exceptions to appropriate HTTP responses:
@RestController
@RequestMapping("/trips")
public class TripController {
@ExceptionHandler(TripServiceException.class)
public ResponseEntity<ErrorResponse> handleTripServiceError(TripServiceException e) {
ErrorResponse errorResponse = new ErrorResponse(
e.getErrorCode(),
e.getUserMessage(),
e.getErrorType().name()
);
int statusCode = getStatusCodeForError(e);
return ResponseEntity.status(statusCode).body(errorResponse);
}
private int getStatusCodeForError(TripServiceException e) {
switch (e.getErrorType()) {
case VALIDATION_ERROR:
return 400;
case BUSINESS_LOGIC_ERROR:
return 409;
case AUTHENTICATION_ERROR:
return 401;
case AUTHORIZATION_ERROR:
return 403;
default:
return 500;
}
}
}
Error Handling in Service Layer
Service layer handles domain-specific error conditions:
@Service
public class TripService {
public void startTrip(String tripId) {
Trip trip = tripRepository.findById(tripId);
if (trip == null) {
throw new TripServiceException(
ErrorType.VALIDATION_ERROR,
"TRIP_NOT_FOUND",
"Trip with ID " + tripId + " does not exist"
);
}
if (!trip.canStart()) {
throw new TripServiceException(
ErrorType.BUSINESS_LOGIC_ERROR,
"TRIP_INVALID_STATE",
"Trip cannot be started in current state: " + trip.getStatus()
);
}
// Proceed with starting the trip
trip.setStatus(TripStatus.IN_PROGRESS);
tripRepository.save(trip);
}
}
This error handling design ensures consistent, structured responses to client requests while maintaining system resilience through circuit breakers and retry mechanisms.
11. Security Design
Input Validation and Sanitization Strategy
All API inputs are validated using a combination of:
- Bean Validation (JSR 380) with Hibernate Validator for structured validation rules
- Custom sanitization for potentially dangerous characters in text fields
- Whitelist-based validation for enum values and allowed character sets
public class TripRequest {
@NotBlank
@Size(max = 100)
private String origin;
@NotBlank
@Size(max = 100)
private String destination;
@NotNull
@Future
private LocalDateTime departureTime;
@ValidTripStatus
private String status;
}
SQL Injection Prevention
- All database queries use parameterized prepared statements
- No dynamic query construction from user input
- ORM framework (Hibernate) is used exclusively for data access
Authentication Middleware Design
- JWT-based authentication with 2-hour expiration
- OAuth2 integration for third-party identity providers (Google, Facebook)
- Authentication filter that validates JWT tokens and sets security context
- API key support for service-to-service communication
Authorization Enforcement
- Role-Based Access Control (RBAC) with the following roles:
TRAVELER: Can view and update their own tripsADMIN: Full access to all trips and system operationsPARTNER: Read-only access to trips in their partner scope
@PreAuthorize("hasRole('ADMIN') or (hasRole('TRAVELER') and @tripAuthorizationService.isOwner(#tripId))")
public void updateTrip(@PathVariable String tripId, @RequestBody TripUpdateRequest request) {
// Implementation
}
Sensitive Data Encryption
- At rest: AES-256 encryption using AWS KMS for trip details and passenger information
- In transit: TLS 1.3 with HSTS header enforcement (HTTPS only)
- PII fields: Passenger names, contact information, and payment details are encrypted
PII Handling and Masking Strategy
- PII fields (names, emails, phone numbers) are encrypted at rest using AES-256
- Logging masks PII fields in logs with
***pattern - Audit trail stores only hashed identifiers, never raw PII
Secrets Management
- AWS Secrets Manager for storing:
- Database credentials
- JWT signing keys
- Third-party API secrets (Google Maps, payment providers)
- Rotation policy: Secrets rotated every 90 days automatically
- Access control: IAM policies restrict secret access to specific services only
Audit Logging Design
- Events logged:
- Trip creation, update, cancellation
- Status transitions (e.g., "Trip marked as completed")
- Access attempts and authorization failures
- Log format includes:
{ "timestamp": "2023-12-01T10:30:45Z", "userId": "user_123456", "operation": "TRIP_UPDATE", "tripId": "trip_789012", "ipAddress": "192.168.1.100", "userAgent": "Mozilla/5.0...", "status": "SUCCESS" } - Retention policy: 90 days for audit logs, archived to S3 for compliance
Security Headers and CORS
- Security headers:
X-Content-Type-Options: nosniffX-Frame-Options: DENYX-XSS-Protection: 1; mode=blockContent-Security-Policy: default-src 'self'
- CORS policy: Strict white-listing of allowed origins with no wildcard domains
Vulnerability Management
- Dependency scanning: Automated scanning of Maven dependencies for known vulnerabilities (OWASP Dependency Check)
- Security code review: Mandatory security review for all PRs with security-focused checklists
- Penetration testing: Quarterly external penetration tests for critical endpoints
12. Testing Strategy
Unit Testing Scope and Coverage Targets
Unit tests will cover 85%+ of business logic and service layer code with 90%+ line coverage for critical paths. Focus on:
- Service method logic (e.g., trip state transitions, validation rules)
- Domain object mutations and boundary conditions
- Error handling paths (invalid state transitions, missing data)
- Strategy pattern implementations (e.g., different trip pricing strategies)
Integration Testing Strategy
Database Integration Tests:
- Use testcontainers with PostgreSQL for real database integration
- Test transaction boundaries and concurrent access patterns
- Validate data consistency across read/write operations
- Verify index usage and query performance
External Service Integration Tests:
- Use WireMock or Mockito for external API dependencies (e.g., payment gateway, notification service)
- Test failure scenarios and retry logic
- Validate event publishing to message broker
Contract Testing
Implement contract testing using Pact for API contracts between services:
- Define consumer-driven contracts for trip service endpoints
- Validate request/response schemas match across services
- Test version compatibility and breaking change detection
Performance Testing Tool
Use k6 for load testing with the following key scenarios:
- Concurrent trip creation and state transitions (1000 concurrent users)
- High-volume state change operations (5000 trips updated per minute)
- Latency targets: p50 < 100ms, p95 < 500ms, p99 < 2s
- Throughput: 1000 trip state transitions/sec target
Test Data Management Strategy
Test Data Factory:
- Implement factory classes for Trip, User, and TripLeg entities
- Use realistic but non-sensitive test data (e.g., mock user IDs, test locations)
- Support parameterized test data generation for edge cases
Test Data Seeding:
- Use fixtures for baseline data (user roles, trip types, state configurations)
- Seed test database with pre-defined valid states for testing transitions
- Maintain clean test database state between test runs using transaction rollback
Test Data Isolation:
- Use separate test database schemas per test environment
- Implement data cleanup strategies to prevent cross-test contamination
- Use UUIDs for test entities to avoid ID conflicts in parallel execution
13. Observability Design
Structured Logging Schema
All logs must follow a consistent schema to enable effective search, filtering, and analysis:
{
"timestamp": "2023-10-27T14:30:00.000Z",
"level": "INFO|WARN|ERROR",
"service": "trip-service",
"trace_id": "12345678-1234-1234-1234-123456789012",
"span_id": "abcdef12-3456-7890-abcd-ef1234567890",
"user_id": "user_12345",
"entity_id": "trip_98765",
"operation": "create_trip",
"message": "Trip created successfully",
"error_code": "TRIP_001",
"error_details": {
"type": "ValidationException",
"message": "Start date cannot be in the past"
},
"duration_ms": 150,
"request_id": "req_abc123",
"tags": ["trip_creation", "user_action"]
}
PII masking rules:
- User email and phone numbers are masked in logs
- Credit card information is completely redacted
- Any sensitive fields in request/response bodies are logged as
***orREDACTED
Distributed Tracing Instrumentation
Using OpenTelemetry for end-to-end tracing:
Custom Spans to Add:
trip_state_machine_transition- for each state changetrip_validation- validation logic executiontrip_event_publisher- async event publishingtrip_data_fetch- data retrieval from DB/cache
Attributes to Capture:
trip.id: Trip identifiertrip.status: Current trip status (e.g., "created", "confirmed")trip.transition: From-to state transitionuser.id: User identifieroperation.name: Operation name like "update_trip"error.type: Exception type if applicable
Metrics Design at Service Level
RED Method Metrics:
| Metric Name | Type | Description | Labels |
|---|---|---|---|
trip_requests_total | Counter | Total trip operations | operation, status |
trip_errors_total | Counter | Trip operation errors | operation, error_code |
trip_duration_seconds | Histogram | Operation duration in seconds | operation, status |
trip_active_count | Gauge | Active trips by status | status |
trip_validation_errors_total | Counter | Validation failures | validation_type |
Example Histogram Configuration:
Histogram tripDuration = OpenTelemetry.get().getMeter("trip-service")
.histogramBuilder("trip_duration_seconds")
.setDescription("Duration of trip operations in seconds")
.setUnit("s")
.build();
Health Check Endpoint Design
GET /health
GET /ready
GET /live
GET /startup
Response Format:
{
"status": "healthy|unhealthy",
"checks": {
"database": {
"status": "healthy",
"details": "Connected to primary DB"
},
"cache": {
"status": "healthy",
"details": "Redis cluster connected"
},
"message_queue": {
"status": "healthy",
"details": "Kafka consumer healthy"
}
},
"timestamp": "2023-10-27T14:30:00.000Z"
}
Alerting Thresholds per Key Metric
| Metric | Alert Level | Threshold | Description |
|---|---|---|---|
trip_errors_total (counter) | Warning | > 5 errors per minute | High error rate |
trip_errors_total (counter) | Critical | > 50 errors per minute | System degradation |
trip_duration_seconds (histogram) | Warning | p95 > 200ms | Slow operations |
trip_duration_seconds (histogram) | Critical | p99 > 1000ms | Performance degradation |
trip_active_count (gauge) | Warning | < 100 active trips | Unexpected low volume |
trip_validation_errors_total (counter) | Warning | > 10 per hour | Validation issues |
On-call escalation path:
- Warning alerts → Slack channel
- Critical alerts → On-call engineer with SMS notification
- Service degradation → Incident response team
Log Aggregation and Analysis
All logs are sent to centralized logging system (e.g., ELK stack or CloudWatch) with:
- Log retention policy: 30 days for INFO, 90 days for ERROR
- Log filtering by service, operation, and error code
- Search capability with user_id, trip_id, trace_id for debugging
Service-Level Metrics Dashboard
Dashboard includes:
- Real-time error rate and latency
- Trip status distribution chart
- Operation success/failure rate by operation type
- Database and cache performance metrics
- Message queue lag indicators
Audit Trail Integration
All state transitions are logged to audit trail:
{
"timestamp": "2023-10-27T14:30:00.000Z",
"user_id": "user_12345",
"trip_id": "trip_98765",
"action": "TRIP_STATUS_CHANGED",
"from_status": "created",
"to_status": "confirmed",
"reason": "User confirmed trip",
"trace_id": "12345678-1234-1234-1234-123456789012"
}
14. Open Questions, Risks & Trade-offs
Decisions requiring product / engineering alignment
- State transition granularity: Should trip states be highly granular (e.g., "booking confirmed", "driver assigned", "trip started") or coarser (e.g., "active", "completed")? The choice affects complexity of state management and user experience.
- Trip cancellation policy: Should trips be cancellable at any time, or only during specific states? This impacts the state machine complexity and business logic.
- Trip rebooking strategy: When a trip is canceled or failed, should the system automatically attempt rebooking, or should it require manual intervention?
- Trip state persistence: Should trip state be persisted in a separate state machine service or co-located with the main trip entity? This affects system decoupling and consistency model.
Known trade-offs and justifications
- State machine complexity vs. flexibility: A highly granular state machine provides better control but increases maintenance overhead and cognitive load for developers. A simpler, coarser-grained state machine reduces complexity but may not support all business requirements.
- Consistency model: Using eventual consistency for state transitions provides better scalability but introduces potential race conditions and temporary inconsistency. Strong consistency (e.g., database transactions) ensures data integrity but may impact performance and scalability.
- Caching vs. real-time state: Caching trip states for faster reads improves performance but introduces staleness risks. Real-time state queries ensure accuracy but may increase latency and database load.
- Event-driven vs. synchronous processing: Event-driven architecture enables loose coupling but adds complexity in error handling and debugging. Synchronous processing is simpler to implement and debug but creates tight coupling between services.
Risks and mitigation strategies
- Race conditions in state transitions: Concurrent updates to trip state can lead to inconsistent states. Mitigation includes optimistic locking, database-level constraints, and idempotent state transitions.
- State inconsistency due to partial failures: If a trip state transition partially completes, the system may end up in an invalid state. Mitigation includes using Saga patterns for distributed transactions and implementing compensating actions.
- Data loss during state transition failures: If a trip's state is updated but the operation fails before persisting to storage, data loss can occur. Mitigation includes implementing an outbox pattern for reliable event publishing and using database transactions.
- Infinite state loops: If the state machine logic has a bug, it could cause an infinite loop of state transitions. Mitigation includes implementing circuit breakers and maximum retry limits for state transitions.
Technical debt acknowledged
- State machine evolution complexity: As business requirements evolve, the state machine may become increasingly complex and harder to maintain. This debt is acknowledged but mitigated by modular design and clear state transition documentation.
- Hardcoded state logic: Initial implementation may include hardcoded state transitions that are difficult to modify or extend. This is addressed by using a strategy pattern for state transition logic and maintaining a clear mapping between states and their behaviors.
- Limited audit trail: The current design may not capture all relevant details for auditing or debugging purposes. This is acknowledged and can be addressed by implementing a comprehensive audit logging system that captures all state transitions with associated metadata.
- No support for complex state conditions: The current design assumes simple state transitions. Complex conditional logic (e.g., "only transition to completed if payment is processed") may not be easily supported and could require refactoring of the state machine design.