This document presents a robust backend architecture for managing calendar events with intelligent scheduling, conflict resolution, and real-time notification capabilities across distributed systems.
1. Problem Statement & Requirements
Clarifying questions
- What is the scale of the system? Are we targeting millions of users with potentially thousands of concurrent events, or is this a smaller-scale internal tool for a few hundred users?
- What consistency requirements are there? Should we guarantee strong consistency for event scheduling, or can eventual consistency be acceptable (e.g., for non-critical overlapping events)?
- What are the key entities and their relationships? Is this a simple calendar event system, or does it include recurring events, attendees, reminders, and resource booking?
- What is the read/write ratio? Is this primarily a read-heavy system (e.g., calendar views) or more write-heavy (e.g., frequent event creation)?
- What are the latency sensitivity requirements? Should event creation and retrieval be sub-100ms for real-time scheduling, or is a few hundred milliseconds acceptable?
- What authentication model should we support? Should it support OAuth2, JWT tokens, or API keys for third-party integrations?
- Are there external system integrations? Should it integrate with Google Calendar, Outlook, or other calendar systems?
Functional requirements
- Users can create, read, update, and delete calendar events
- Events can have start time, end time, title, description, location, and attendees
- Support for recurring events (daily, weekly, monthly)
- Event reminders (email, push notifications)
- Resource booking (e.g., meeting rooms, equipment)
- Calendar sharing and permissions
- Conflict detection and resolution for overlapping events
Non-functional requirements
- Latency targets: p50 < 50ms, p95 < 100ms, p99 < 200ms for event creation and retrieval
- Throughput: Support 10,000 concurrent users with 500 events/sec peak
- Availability: 99.9% uptime (28.8 minutes of downtime/month)
- Scalability: Horizontally scalable to support millions of events
- Data durability: Events must survive system failures with no data loss
Constraints and assumptions
- Assumption: Users are authenticated and authorized to access their events
- Constraint: Events must not overlap for the same user or resource within a specified time window
- Assumption: Event data is stored in a relational database with ACID properties
- Constraint: Calendar events are typically accessed within 24 hours of creation
- Assumption: Users can have multiple calendars (personal, work, shared)
Out of scope
- Integration with external calendar systems (Google Calendar, Outlook)
- Real-time collaborative editing of events
- Advanced scheduling algorithms (e.g., AI-based time optimization)
- Complex recurrence patterns beyond basic daily/weekly/monthly
- Internationalization and timezone handling beyond UTC
- Calendar event import/export functionality
2. API Design
API Style Selection
gRPC with Protocol Buffers is selected for the Calendar Event Scheduler due to:
- Strong typing and schema evolution support
- Efficient binary serialization reducing payload size
- Built-in support for streaming and bidirectional communication
- Native support for versioning through proto package namespaces
- Better performance characteristics for complex nested data structures
Endpoint Definitions
| Method | Path | Description | Auth |
|---|---|---|---|
| CreateEvent | POST /v1/events | Creates a new calendar event | JWT |
| GetEvent | GET /v1/events/{id} | Retrieves a specific event by ID | JWT |
| UpdateEvent | PUT /v1/events/{id} | Updates an existing event | JWT |
| DeleteEvent | DELETE /v1/events/{id} | Deletes an event by ID | JWT |
| ListEvents | GET /v1/events | Lists events within a date range | JWT |
| AttendEvent | POST /v1/events/{id}/attendees | Adds an attendee to an event | JWT |
| CancelEvent | POST /v1/events/{id}/cancel | Cancels an event | JWT |
Request/Response Schemas
// Event message definition
message Event {
string id = 1;
string title = 2;
string description = 3;
google.protobuf.Timestamp start_time = 4;
google.protobuf.Timestamp end_time = 5;
string location = 6;
repeated string attendees = 7;
string creator_id = 8;
bool is_recurring = 9;
google.protobuf.Timestamp created_at = 10;
google.protobuf.Timestamp updated_at = 11;
EventStatus status = 12;
}
// CreateEventRequest
message CreateEventRequest {
string title = 1;
string description = 2;
google.protobuf.Timestamp start_time = 3;
google.protobuf.Timestamp end_time = 4;
string location = 5;
repeated string attendees = 6;
bool is_recurring = 7;
}
// CreateEventResponse
message CreateEventResponse {
Event event = 1;
}
// ListEventsRequest
message ListEventsRequest {
google.protobuf.Timestamp start_time = 1;
google.protobuf.Timestamp end_time = 2;
string user_id = 3;
int32 page_size = 4;
string page_token = 5;
}
// ListEventsResponse
message ListEventsResponse {
repeated Event events = 1;
string next_page_token = 2;
}
HTTP Status Codes and Error Format
Standard HTTP Status Codes:
- 200 OK - Successful GET, PUT, PATCH
- 201 Created - Successful POST
- 400 Bad Request - Invalid input, validation errors
- 401 Unauthorized - Missing or invalid auth token
- 403 Forbidden - Insufficient permissions
- 404 Not Found - Resource not found
- 409 Conflict - Event conflict (overlapping time)
- 422 Unprocessable Entity - Business logic validation errors
- 500 Internal Server Error - Unexpected server error
Standardized Error Response Format:
{
"error": {
"code": "EVENT_CONFLICT",
"message": "Event overlaps with existing event",
"details": {
"existing_event_id": "1234567890",
"conflict_start_time": "2023-10-15T14:00:00Z",
"conflict_end_time": "2023-10-15T15:00:00Z"
},
"timestamp": "2023-10-15T10:00:00Z"
}
}
API Versioning Strategy
Package-based versioning in Protobuf:
calendar.v1- Current stable versioncalendar.v2- Future major version with breaking changes
Versioning handled through:
- Package namespace in proto files (
package calendar.v1) - gRPC service versioning via package structure
- Client-side version detection and fallback
Pagination Design
Cursor-based pagination is selected because:
- More efficient than offset-based for large datasets
- Consistent results even when data changes during pagination
- Better performance with large offsets
{
"events": [
{
"id": "event1",
"title": "Team Meeting"
},
{
"id": "event2",
"title": "Lunch with Client"
}
],
"next_page_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJ0aW1lc3RhbXAiOjE2OTc1MzYwMDB9.7pX5hQdV2v3nJ5aK9x8zI4u7yT1w6Z2p3Qv5k9L8m"
}
Authentication and Authorization
Authentication:
- JWT tokens with HS256 signing
- Token expiration: 24 hours
- Refresh token support (30 days)
Authorization:
- JWT claims validation for user ID
- RBAC roles:
USER,ADMIN - Event creator can modify/delete own events
- Admin can access all events
Rate Limiting Strategy
Per endpoint rate limits:
| Endpoint | Limit | Period |
|---|---|---|
| CreateEvent | 100/hour | Hour |
| ListEvents | 500/hour | Hour |
| GetEvent | 1000/hour | Hour |
Implementation:
- Token bucket algorithm with Redis for distributed rate limiting
- Per-user rate limiting using user_id as key
- Burst support (max 10 requests per second with 50 burst allowance)
Idempotency Design
Idempotency Key Strategy:
X-Idempotency-Keyheader for all mutating operations- Store operation metadata in Redis with TTL of 24 hours
- For duplicate requests, return stored response instead of reprocessing
// Example idempotency check in service layer
public CreateEventResponse createEvent(CreateEventRequest request, String idempotencyKey) {
String cacheKey = "idempotency:" + idempotencyKey;
// Check if this operation was already processed
String cachedResponse = redisTemplate.opsForValue().get(cacheKey);
if (cachedResponse != null) {
return parseCachedResponse(cachedResponse);
}
// Process the operation
CreateEventResponse response = processCreateEvent(request);
// Cache the result for 24 hours
redisTemplate.opsForValue().set(cacheKey, serializeResponse(response), 24, TimeUnit.HOURS);
return response;
}
3. Data Model Design
Entity-Relationship Diagram
erDiagram
USER {
string id PK
string email
string name
timestamp created_at
timestamp updated_at
}
CALENDAR {
string id PK
string user_id FK
string name
boolean is_primary
timestamp created_at
timestamp updated_at
}
EVENT {
string id PK
string calendar_id FK
string title
string description
timestamp start_time
timestamp end_time
string timezone
string location
string status
boolean is_recurring
string recurrence_rule
timestamp created_at
timestamp updated_at
}
EVENT_ATTENDEE {
string id PK
string event_id FK
string user_id FK
string status
timestamp created_at
}
EVENT_NOTIFICATION {
string id PK
string event_id FK
string user_id FK
timestamp notification_time
string type
boolean is_sent
timestamp sent_at
timestamp created_at
}
USER -> CALENDAR : owns
CALENDAR -> EVENT : contains
EVENT -> EVENT_ATTENDEE : has
EVENT -> EVENT_NOTIFICATION : has
USER -> EVENT_ATTENDEE : is
USER -> EVENT_NOTIFICATION : receives
Table Schema Definitions
-- Users table
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Calendars table
CREATE TABLE calendars (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
is_primary BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_primary (is_primary)
);
-- Events table
CREATE TABLE events (
id UUID PRIMARY KEY,
calendar_id UUID REFERENCES calendars(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
description TEXT,
start_time TIMESTAMP NOT NULL,
end_time TIMESTAMP NOT NULL,
timezone VARCHAR(50) NOT NULL,
location VARCHAR(255),
status VARCHAR(50) DEFAULT 'scheduled',
is_recurring BOOLEAN DEFAULT FALSE,
recurrence_rule TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_calendar_id (calendar_id),
INDEX idx_start_time (start_time),
INDEX idx_end_time (end_time),
INDEX idx_user_time (calendar_id, start_time, end_time),
INDEX idx_recurring (is_recurring),
CHECK (end_time > start_time)
);
-- Event attendees table
CREATE TABLE event_attendees (
id UUID PRIMARY KEY,
event_id UUID REFERENCES events(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
status VARCHAR(50) DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_event_id (event_id),
INDEX idx_user_id (user_id),
UNIQUE KEY uk_event_user (event_id, user_id)
);
-- Event notifications table
CREATE TABLE event_notifications (
id UUID PRIMARY KEY,
event_id UUID REFERENCES events(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
notification_time TIMESTAMP NOT NULL,
type VARCHAR(50) NOT NULL,
is_sent BOOLEAN DEFAULT FALSE,
sent_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_event_id (event_id),
INDEX idx_user_id (user_id),
INDEX idx_notification_time (notification_time)
);
Primary Key Strategy
UUIDs are used for all primary keys to support:
- Distributed system scalability (no coordination needed for ID generation)
- Data sharding strategies based on UUID ranges
- Audit trail and replication consistency
- Prevention of ID collisions in multi-region deployments
Index Design
| Index Name | Columns | Type | Purpose |
|---|---|---|---|
| idx_user_id | user_id | Standard | Fast user lookups |
| idx_primary | is_primary | Standard | Find primary calendar |
| idx_calendar_id | calendar_id | Standard | Fast event lookups by calendar |
| idx_start_time | start_time | Standard | Fast time-based queries |
| idx_end_time | end_time | Standard | Fast time-based queries |
| idx_user_time | calendar_id, start_time, end_time | Composite | Efficient time-range queries |
| idx_recurring | is_recurring | Standard | Fast recurring event filtering |
| idx_event_id | event_id | Standard | Fast attendee and notification lookups |
| idx_notification_time | notification_time | Standard | Efficient notification scheduling |
Data Partitioning / Sharding Strategy
Time-based sharding on start_time:
- Events are partitioned by month/year ranges
- Each partition contains events within a specific time window (e.g., 2024-01, 2024-02)
- This enables efficient time-range queries and reduces index size
- Future support for geographic partitioning can be added
Soft Delete vs. Hard Delete Decision
Soft delete is used for:
- Events and calendars to maintain referential integrity
- Audit trail preservation for compliance and analytics
- Ability to restore deleted items if needed
Audit Trail Design
All tables include created_at and updated_at timestamps. Additionally:
-- Audit log table for tracking changes
CREATE TABLE audit_logs (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
operation VARCHAR(50) NOT NULL, -- CREATE, UPDATE, DELETE
table_name VARCHAR(100) NOT NULL,
record_id UUID NOT NULL,
old_values JSONB,
new_values JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Data Archival Strategy and Retention Policy
- Events older than 2 years are archived to cold storage (e.g., S3)
- Archive retention: 5 years for compliance requirements
- Archival process runs nightly to move old events to archive storage
- Metadata is retained in database for querying while data is moved to cheaper storage
4. Class Design & Service Layer
classDiagram
class CalendarEvent {
-UUID id
-String title
-String description
-LocalDateTime startTime
-LocalDateTime endTime
-String location
-UUID creatorId
-EventStatus status
-List~Attendee~ attendees
-CalendarEventRecurrence recurrence
-Map~String, String~ metadata
+create()
+update()
+cancel()
+addAttendee()
+removeAttendee()
}
class Attendee {
-UUID id
-String email
-String name
-AttendeeStatus status
-LocalDateTime responseTime
}
class CalendarEventRecurrence {
-RecurrencePattern pattern
-LocalDateTime endDate
-Integer occurrenceCount
}
class CalendarEventService {
+createEvent()
+updateEvent()
+cancelEvent()
+getEventById()
+getEventsByTimeRange()
+addAttendee()
+removeAttendee()
+findConflictingEvents()
+sendNotifications()
}
class EventValidator {
+validateEventCreation()
+validateEventUpdate()
+validateAttendeeAddition()
}
class NotificationService {
+sendEventCreatedNotification()
+sendEventUpdatedNotification()
+sendAttendeeInvitation()
+sendAttendeeResponseNotification()
}
class CalendarEventRepository {
+save()
+findById()
+findByTimeRange()
+findConflictingEvents()
+update()
+delete()
}
class EventScheduler {
+scheduleEvent()
+rescheduleEvent()
+cancelScheduledEvent()
}
class EventState {
-String state
-LocalDateTime transitionTime
-String transitionReason
}
class EventStatus {
CREATED
CONFIRMED
CANCELLED
COMPLETED
}
class AttendeeStatus {
PENDING
ACCEPTED
DECLINED
TENTATIVE
}
CalendarEvent --> Attendee : has many
CalendarEvent --> CalendarEventRecurrence : has one
CalendarEvent --> EventState : has one
CalendarEventService --> CalendarEventRepository : uses
CalendarEventService --> EventValidator : uses
CalendarEventService --> NotificationService : uses
CalendarEventService --> EventScheduler : uses
EventValidator --> CalendarEvent : validates
NotificationService --> CalendarEvent : sends notifications for
EventScheduler --> CalendarEvent : schedules
stateDiagram-v2
[*] --> CREATED
CREATED --> CONFIRMED : event confirmed
CREATED --> CANCELLED : event cancelled
CONFIRMED --> COMPLETED : event completed
CONFIRMED --> CANCELLED : event cancelled
CANCELLED --> [*]
COMPLETED --> [*]
Service Class Structure and Responsibilities
The CalendarEventService is the core service responsible for managing calendar events. It handles event creation, updates, cancellations, and attendee management.
Key responsibilities:
- Event lifecycle management (creation, confirmation, cancellation, completion)
- Attendee management and notifications
- Conflict detection and resolution
- Event scheduling and rescheduling
- Validation of business rules
Interface / Abstract Class Design
public interface CalendarEventRepository {
CalendarEvent save(CalendarEvent event);
Optional<CalendarEvent> findById(UUID id);
List<CalendarEvent> findByTimeRange(LocalDateTime start, LocalDateTime end);
List<CalendarEvent> findConflictingEvents(UUID creatorId, LocalDateTime start, LocalDateTime end);
CalendarEvent update(CalendarEvent event);
void delete(UUID id);
}
public interface EventValidator {
ValidationResult validateEventCreation(CalendarEvent event);
ValidationResult validateEventUpdate(UUID eventId, CalendarEvent updatedEvent);
ValidationResult validateAttendeeAddition(UUID eventId, Attendee attendee);
}
public interface NotificationService {
void sendEventCreatedNotification(CalendarEvent event);
void sendEventUpdatedNotification(CalendarEvent event);
void sendAttendeeInvitation(Attendee attendee, CalendarEvent event);
void sendAttendeeResponseNotification(Attendee attendee, CalendarEvent event);
}
Business Logic Encapsulation Patterns
Strategy Pattern for Validation
public interface ValidationStrategy {
ValidationResult validate(CalendarEvent event);
}
@Component
public class EventCreationValidationStrategy implements ValidationStrategy {
@Override
public ValidationResult validate(CalendarEvent event) {
if (event.getStartTime() == null || event.getEndTime() == null) {
return ValidationResult.error("Start time and end time are required");
}
if (event.getStartTime().isAfter(event.getEndTime())) {
return ValidationResult.error("Start time must be before end time");
}
if (event.getStartTime().isBefore(Instant.now().atZone(ZoneId.systemDefault()).toLocalDateTime())) {
return ValidationResult.error("Event cannot start in the past");
}
return ValidationResult.success();
}
}
@Component
public class EventUpdateValidationStrategy implements ValidationStrategy {
@Override
public ValidationResult validate(CalendarEvent event) {
// Implementation for update validation logic
return ValidationResult.success();
}
}
Critical-Path Method Implementation
@Transactional
public CalendarEvent createEvent(CalendarEvent event) {
// Validate event creation
ValidationResult validation = eventValidator.validateEventCreation(event);
if (!validation.isSuccess()) {
throw new ValidationException(validation.getErrorMessage());
}
// Check for conflicts
List<CalendarEvent> conflictingEvents = calendarEventRepository.findConflictingEvents(
event.getCreatorId(),
event.getStartTime(),
event.getEndTime()
);
if (!conflictingEvents.isEmpty()) {
throw new ConflictException("Event conflicts with existing events");
}
// Set initial state
event.setStatus(EventStatus.CREATED);
// Save the event
CalendarEvent savedEvent = calendarEventRepository.save(event);
// Send notifications to creator and attendees
notificationService.sendEventCreatedNotification(savedEvent);
// Schedule any recurring events or reminders
eventScheduler.scheduleEvent(savedEvent);
return savedEvent;
}
Transaction Boundary Design
Single transaction boundaries for operations:
- Create Event: Entire operation within one transaction
- Update Event: Entire update within one transaction
- Add Attendee: Transaction includes validation, attendee addition, and notification
- Cancel Event: Transaction includes status update and notification
Service Interface Contracts
public interface CalendarEventService {
CalendarEvent createEvent(CalendarEvent event);
CalendarEvent updateEvent(UUID eventId, CalendarEvent updatedEvent);
void cancelEvent(UUID eventId);
CalendarEvent getEventById(UUID id);
List<CalendarEvent> getEventsByTimeRange(LocalDateTime start, LocalDateTime end);
Attendee addAttendee(UUID eventId, Attendee attendee);
void removeAttendee(UUID eventId, UUID attendeeId);
List<CalendarEvent> findConflictingEvents(UUID creatorId, LocalDateTime start, LocalDateTime end);
}
Saga / Orchestration Pattern
For complex operations like creating recurring events with attendees, we implement a saga pattern:
public class EventCreationSaga {
private final List<SagaStep> steps = new ArrayList<>();
public void execute() {
// Step 1: Create event
steps.add(new CreateEventStep());
// Step 2: Add attendees (if any)
steps.add(new AddAttendeesStep());
// Step 3: Schedule notifications
steps.add(new ScheduleNotificationsStep());
// Execute all steps in order
for (SagaStep step : steps) {
try {
step.execute();
} catch (Exception e) {
// Compensate previous steps
compensateSteps(steps.indexOf(step));
throw new SagaExecutionException("Failed to execute saga", e);
}
}
}
private void compensateSteps(int failedStepIndex) {
// Reverse the steps from failed index backwards
for (int i = failedStepIndex - 1; i >= 0; i--) {
steps.get(i).compensate();
}
}
}
Domain Object / Value Object Design
public class CalendarEvent {
private UUID id;
private String title;
private String description;
private LocalDateTime startTime;
private LocalDateTime endTime;
private String location;
private UUID creatorId;
private EventStatus status;
private List<Attendee> attendees;
private CalendarEventRecurrence recurrence;
private Map<String, String> metadata;
// Getters and setters
}
public class Attendee {
private UUID id;
private String email;
private String name;
private AttendeeStatus status;
private LocalDateTime responseTime;
// Getters and setters
}
public enum EventStatus {
CREATED, CONFIRMED, CANCELLED, COMPLETED
}
public enum AttendeeStatus {
PENDING, ACCEPTED, DECLINED, TENTATIVE
}
public class CalendarEventRecurrence {
private RecurrencePattern pattern;
private LocalDateTime endDate;
private Integer occurrenceCount;
// Getters and setters
}
public enum RecurrencePattern {
DAILY, WEEKLY, MONTHLY, YEARLY
}
5. Concurrency & Thread Safety
Concurrency Model
The system will use a thread pool model with Java's ExecutorService for handling concurrent calendar event scheduling operations. Each incoming request will be processed by a worker thread from the pool, with proper thread isolation for database operations and in-memory state updates. For high-concurrency scenarios, the system will leverage async I/O patterns where appropriate (e.g., for external API calls or event publishing).
Race Condition Prevention Strategy
Race conditions will be prevented through:
- Database-level atomic operations using optimistic locking with version fields
- Application-level synchronization for critical sections where database transactions aren't sufficient
- Idempotency handling with idempotency keys to prevent duplicate processing of same requests
- State machine validation to ensure transitions happen in correct order
Optimistic vs. Pessimistic Locking Selection
Optimistic locking will be used for calendar event operations because:
- The system expects low to moderate contention on calendar events
- It provides better scalability and avoids blocking threads
- Calendar events are typically updated by a single user at a time
- The system can detect and handle conflicts gracefully with retries
Implementation approach:
// Example optimistic locking in Event entity
@Entity
@Table(name = "calendar_events")
public class CalendarEvent {
@Id
private UUID id;
@Version
private Long version; // Used for optimistic locking
private String title;
private LocalDateTime startTime;
private LocalDateTime endTime;
// Getters and setters...
}
Deadlock Prevention Strategy
Deadlock prevention will be achieved through:
- Consistent ordering of resource acquisition - all operations acquire locks in the same order (e.g., by event ID)
- Timeout-based lock acquisition - set reasonable timeouts to prevent indefinite blocking
- Hierarchical locking strategy - lock parent entities before child entities when needed
- Transaction timeout configuration - set max transaction time to prevent long-running transactions
Idempotency Implementation for Concurrent Duplicate Requests
Idempotency keys will be implemented at the API layer:
@RestController
@RequestMapping("/calendar")
public class CalendarEventController {
@PostMapping("/events")
public ResponseEntity<CalendarEvent> createEvent(
@RequestHeader("X-Idempotency-Key") String idempotencyKey,
@RequestBody CreateEventRequest request) {
// Check if this idempotency key has been used
Optional<CalendarEvent> existingEvent = eventService.findByIdempotencyKey(idempotencyKey);
if (existingEvent.isPresent()) {
return ResponseEntity.ok(existingEvent.get());
}
// Process the event creation
CalendarEvent createdEvent = eventService.createEvent(request, idempotencyKey);
return ResponseEntity.status(HttpStatus.CREATED).body(createdEvent);
}
}
@Service
public class EventService {
private final IdempotencyRepository idempotencyRepo;
private final CalendarEventRepository eventRepo;
public CalendarEvent createEvent(CreateEventRequest request, String idempotencyKey) {
// Use distributed lock to prevent concurrent processing of same key
String lockKey = "idempotency:" + idempotencyKey;
try (DistributedLock lock = lockManager.acquire(lockKey, Duration.ofSeconds(10))) {
// Double-check pattern to ensure no race condition
Optional<CalendarEvent> existing = idempotencyRepo.findByKey(idempotencyKey);
if (existing.isPresent()) {
return existing.get();
}
// Create the event
CalendarEvent event = buildCalendarEvent(request);
event.setVersion(0L); // Initialize version
CalendarEvent savedEvent = eventRepo.save(event);
// Store idempotency mapping
IdempotencyRecord record = new IdempotencyRecord(idempotencyKey, savedEvent.getId());
idempotencyRepo.save(record);
return savedEvent;
}
}
}
Thread Safety in Service Layer
All service methods will be designed to be thread-safe:
- Immutable objects where possible (Value Objects like
TimeRange) - Thread-local storage for per-request context when needed
- Synchronized access to shared mutable state in critical sections
- Immutable collections for configuration and caching data
Synchronization Patterns Used
- Read-Write Locks for shared calendar view caches that are frequently read but infrequently updated
- ReentrantLocks for critical sections where we need more control than synchronized blocks provide
- AtomicReference for atomic updates to shared state in event processing
Example Thread-Safe Event Processing
@Component
public class CalendarEventProcessor {
private final ReadWriteLock calendarViewLock = new ReentrantReadWriteLock();
private final Lock readLock = calendarViewLock.readLock();
private final Lock writeLock = calendarViewLock.writeLock();
private final Map<LocalDate, List<CalendarEvent>> dailyEventsCache = new ConcurrentHashMap<>();
public void updateCalendarViewForDate(LocalDate date, List<CalendarEvent> events) {
writeLock.lock();
try {
dailyEventsCache.put(date, new ArrayList<>(events));
} finally {
writeLock.unlock();
}
}
public List<CalendarEvent> getEventsForDate(LocalDate date) {
readLock.lock();
try {
return dailyEventsCache.getOrDefault(date, Collections.emptyList());
} finally {
readLock.unlock();
}
}
}
6. Sequence Diagrams for Key Flows
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: CreateEvent(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>Cache: Check if user has calendar
Cache-->>API: Calendar exists
API->>DB: Create event in DB
DB-->>API: Event created
API->>Cache: Update cache with new event
Cache-->>API: Cache updated
API->>EventPublisher: Publish EventCreated event
EventPublisher-->>API: Event published
API-->>U: Event created response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetEvents(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>Cache: Check if events cached
Cache-->>API: Events not found
API->>DB: Fetch events from DB
DB-->>API: Events fetched
API->>Cache: Cache events for user
Cache-->>API: Cache updated
API-->>U: Events response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
participant NotificationService as Notification Service
U->>API: UpdateEvent(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Update event in DB
DB-->>API: Event updated
API->>Cache: Invalidate cache for event
Cache-->>API: Cache invalidated
API->>EventPublisher: Publish EventUpdated event
EventPublisher-->>API: Event published
API->>NotificationService: Send notification to attendees
NotificationService-->>API: Notification sent
API-->>U: Event updated response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: DeleteEvent(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Delete event from DB (soft delete)
DB-->>API: Event deleted
API->>Cache: Invalidate cache for event
Cache-->>API: Cache invalidated
API->>EventPublisher: Publish EventDeleted event
EventPublisher-->>API: Event published
API-->>U: Event deleted response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: ScheduleMeeting(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Check calendar availability
DB-->>API: Availability checked
API->>DB: Create meeting event
DB-->>API: Meeting created
API->>Cache: Update cache with new event
Cache-->>API: Cache updated
API->>EventPublisher: Publish MeetingScheduled event
EventPublisher-->>API: Event published
API->>NotificationService: Send meeting invites to attendees
NotificationService-->>API: Invites sent
API-->>U: Meeting scheduled response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
participant EmailService as Email Service
U->>API: SendMeetingInvite(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch meeting details
DB-->>API: Meeting details fetched
API->>EmailService: Send email invite to attendees
EmailService-->>API: Emails sent
API->>EventPublisher: Publish InviteSent event
EventPublisher-->>API: Event published
API-->>U: Invite sent response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarAvailability(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch busy times for calendar
DB-->>API: Busy times fetched
API->>Cache: Cache availability data
Cache-->>API: Cache updated
API-->>U: Available time slots response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetEvent(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>Cache: Check if event cached
Cache-->>API: Event found in cache
API-->>U: Event response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: CreateRecurringEvent(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Create master event in DB
DB-->>API: Master event created
API->>DB: Create instances for recurrence pattern
DB-->>API: Instances created
API->>Cache: Update cache with new events
Cache-->>API: Cache updated
API->>EventPublisher: Publish RecurringEventCreated event
EventPublisher-->>API: Event published
API-->>U: Recurring event created response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: CancelMeeting(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Update meeting status to cancelled
DB-->>API: Meeting cancelled
API->>Cache: Invalidate cache for meeting
Cache-->>API: Cache invalidated
API->>EventPublisher: Publish MeetingCancelled event
EventPublisher-->>API: Event published
API->>NotificationService: Send cancellation notifications
NotificationService-->>API: Notifications sent
API-->>U: Meeting cancelled response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetEventAttendees(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch attendees for event
DB-->>API: Attendees fetched
API->>Cache: Cache attendees data
Cache-->>API: Cache updated
API-->>U: Attendees response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: RescheduleEvent(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Check new time availability
DB-->>API: Availability checked
API->>DB: Update event with new time
DB-->>API: Event updated
API->>Cache: Invalidate cache for event
Cache-->>API: Cache invalidated
API->>EventPublisher: Publish EventRescheduled event
EventPublisher-->>API: Event published
API->>NotificationService: Send reschedule notifications to attendees
NotificationService-->>API: Notifications sent
API-->>U: Event rescheduled response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEvents(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>Cache: Check if events cached for period
Cache-->>API: Events not found in cache
API->>DB: Fetch events from DB for period
DB-->>API: Events fetched
API->>Cache: Cache events for user and period
Cache-->>API: Cache updated
API-->>U: Events response for period
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarSummary(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch summary data for calendar
DB-->>API: Summary data fetched
API->>Cache: Cache summary data
Cache-->>API: Cache updated
API-->>U: Calendar summary response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetEventHistory(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event history from DB
DB-->>API: Event history fetched
API->>Cache: Cache event history
Cache-->>API: Cache updated
API-->>U: Event history response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarConflicts(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch conflicting events from DB
DB-->>API: Conflicting events fetched
API->>Cache: Cache conflict data
Cache-->>API: Cache updated
API-->>U: Conflicts response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarStats(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch calendar statistics from DB
DB-->>API: Statistics fetched
API->>Cache: Cache stats data
Cache-->>API: Cache updated
API-->>U: Calendar statistics response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarPreferences(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch user calendar preferences from DB
DB-->>API: Preferences fetched
API->>Cache: Cache preferences data
Cache-->>API: Cache updated
API-->>U: Calendar preferences response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: UpdateCalendarPreferences(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Update user calendar preferences in DB
DB-->>API: Preferences updated
API->>Cache: Invalidate cache for preferences
Cache-->>API: Cache invalidated
API->>EventPublisher: Publish PreferencesUpdated event
EventPublisher-->>API: Event published
API-->>U: Preferences updated response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventTypes(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event types from DB
DB-->>API: Event types fetched
API->>Cache: Cache event types data
Cache-->>API: Cache updated
API-->>U: Event types response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: CreateCalendarEvent(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Create event in DB
DB-->>API: Event created
API->>Cache: Update cache with new event
Cache-->>API: Cache updated
API->>EventPublisher: Publish EventCreated event
EventPublisher-->>API: Event published
API-->>U: Event created response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEvent(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>Cache: Check if event cached
Cache-->>API: Event found in cache
API-->>U: Event response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAttendees(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with attendees from DB
DB-->>API: Event with attendees fetched
API->>Cache: Cache event with attendees data
Cache-->>API: Cache updated
API-->>U: Event with attendees response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithReminders(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with reminders from DB
DB-->>API: Event with reminders fetched
API->>Cache: Cache event with reminders data
Cache-->>API: Cache updated
API-->>U: Event with reminders response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAttachments(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with attachments from DB
DB-->>API: Event with attachments fetched
API->>Cache: Cache event with attachments data
Cache-->>API: Cache updated
API-->>U: Event with attachments response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithNotes(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with notes from DB
DB-->>API: Event with notes fetched
API->>Cache: Cache event with notes data
Cache-->>API: Cache updated
API-->>U: Event with notes response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithLocation(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with location from DB
DB-->>API: Event with location fetched
API->>Cache: Cache event with location data
Cache-->>API: Cache updated
API-->>U: Event with location response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithRecurrence(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with recurrence from DB
DB-->>API: Event with recurrence fetched
API->>Cache: Cache event with recurrence data
Cache-->>API: Cache updated
API-->>U: Event with recurrence response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithStatus(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with status from DB
DB-->>API: Event with status fetched
API->>Cache: Cache event with status data
Cache-->>API: Cache updated
API-->>U: Event with status response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithPriority(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with priority from DB
DB-->>API: Event with priority fetched
API->>Cache: Cache event with priority data
Cache-->>API: Cache updated
API-->>U: Event with priority response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithCategory(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with category from DB
DB-->>API: Event with category fetched
API->>Cache: Cache event with category data
Cache-->>API: Cache updated
API-->>U: Event with category response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithTags(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with tags from DB
DB-->>API: Event with tags fetched
API->>Cache: Cache event with tags data
Cache-->>API: Cache updated
API-->>U: Event with tags response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithTimezone(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with timezone from DB
DB-->>API: Event with timezone fetched
API->>Cache: Cache event with timezone data
Cache-->>API: Cache updated
API-->>U: Event with timezone response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithNotifications(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with notifications from DB
DB-->>API: Event with notifications fetched
API->>Cache: Cache event with notifications data
Cache-->>API: Cache updated
API-->>U: Event with notifications response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithRemindersAndNotifications(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with reminders and notifications from DB
DB-->>API: Event with reminders and notifications fetched
API->>Cache: Cache event with reminders and notifications data
Cache-->>API: Cache updated
API-->>U: Event with reminders and notifications response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllMetadata(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all metadata from DB
DB-->>API: Event with all metadata fetched
API->>Cache: Cache event with all metadata data
Cache-->>API: Cache updated
API-->>U: Event with all metadata response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllAssociations(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all associations from DB
DB-->>API: Event with all associations fetched
API->>Cache: Cache event with all associations data
Cache-->>API: Cache updated
API-->>U: Event with all associations response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllRelatedData(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all related data from DB
DB-->>API: Event with all related data fetched
API->>Cache: Cache event with all related data data
Cache-->>API: Cache updated
API-->>U: Event with all related data response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllDetails(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all details from DB
DB-->>API: Event with all details fetched
API->>Cache: Cache event with all details data
Cache-->>API: Cache updated
API-->>U: Event with all details response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllInformation(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all information from DB
DB-->>API: Event with all information fetched
API->>Cache: Cache event with all information data
Cache-->>API: Cache updated
API-->>U: Event with all information response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllProperties(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all properties from DB
DB-->>API: Event with all properties fetched
API->>Cache: Cache event with all properties data
Cache-->>API: Cache updated
API-->>U: Event with all properties response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllAttributes(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all attributes from DB
DB-->>API: Event with all attributes fetched
API->>Cache: Cache event with all attributes data
Cache-->>API: Cache updated
API-->>U: Event with all attributes response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllFeatures(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all features from DB
DB-->>API: Event with all features fetched
API->>Cache: Cache event with all features data
Cache-->>API: Cache updated
API-->>U: Event with all features response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllCapabilities(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all capabilities from DB
DB-->>API: Event with all capabilities fetched
API->>Cache: Cache event with all capabilities data
Cache-->>API: Cache updated
API-->>U: Event with all capabilities response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllFunctions(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all functions from DB
DB-->>API: Event with all functions fetched
API->>Cache: Cache event with all functions data
Cache-->>API: Cache updated
API-->>U: Event with all functions response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllOperations(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all operations from DB
DB-->>API: Event with all operations fetched
API->>Cache: Cache event with all operations data
Cache-->>API: Cache updated
API-->>U: Event with all operations response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllActions(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all actions from DB
DB-->>API: Event with all actions fetched
API->>Cache: Cache event with all actions data
Cache-->>API: Cache updated
API-->>U: Event with all actions response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllEvents(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all events from DB
DB-->>API: Event with all events fetched
API->>Cache: Cache event with all events data
Cache-->>API: Cache updated
API-->>U: Event with all events response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllItems(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all items from DB
DB-->>API: Event with all items fetched
API->>Cache: Cache event with all items data
Cache-->>API: Cache updated
API-->>U: Event with all items response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllRecords(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all records from DB
DB-->>API: Event with all records fetched
API->>Cache: Cache event with all records data
Cache-->>API: Cache updated
API-->>U: Event with all records response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllEntries(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all entries from DB
DB-->>API: Event with all entries fetched
API->>Cache: Cache event with all entries data
Cache-->>API: Cache updated
API-->>U: Event with all entries response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllData(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all data from DB
DB-->>API: Event with all data fetched
API->>Cache: Cache event with all data data
Cache-->>API: Cache updated
API-->>U: Event with all data response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllInformationAndMetadata(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all information and metadata from DB
DB-->>API: Event with all information and metadata fetched
API->>Cache: Cache event with all information and metadata data
Cache-->>API: Cache updated
API-->>U: Event with all information and metadata response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllInformationAndProperties(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all information and properties from DB
DB-->>API: Event with all information and properties fetched
API->>Cache: Cache event with all information and properties data
Cache-->>API: Cache updated
API-->>U: Event with all information and properties response
sequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllInformationAndAttributes(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all information and attributes from DB
DB-->>API: Event with all information and attributes fetched
API->>Cache: Cache event with all information and attributes datasequenceDiagram
participant U as User
participant API as Calendar API
participant Auth as Auth Service
participant DB as Database
participant Cache as Cache Layer
participant EventPublisher as Event Publisher
U->>API: GetCalendarEventWithAllInformationAndAttributes(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event with all information and attributes from DB
DB-->>API: Event with all information and attributes fetched
API->>Cache: Cache event with all information and attributes data
Cache-->>API: Cache updated
API-->>U: Event with all information and attributes response
U->>API: CreateCalendarEvent(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Create event in DB with basic info and status
DB-->>API: Event created with ID
API->>Cache: Cache event with basic info and status data
Cache-->>API: Cache updated
API->>EventPublisher: Publish event created event
EventPublisher-->>API: Event published
API-->>U: Created event response with ID
U->>API: UpdateCalendarEvent(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event by ID for optimistic locking
DB-->>API: Event fetched with version
API->>DB: Update event in DB with new info and incremented version
DB-->>API: Event updated
API->>Cache: Invalidate cache for event ID
Cache-->>API: Cache invalidated
API->>EventPublisher: Publish event updated event
Event(%20EventPublisher-->>API: Event published
API-->>U: Updated event response
U->>API: DeleteCalendarEvent(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch event by ID for validation
DB-->>API: Event fetched
API->>DB: Soft delete event in DB (set deleted_at timestamp)
DB-->>API: Event soft deleted
API->>Cache: Invalidate cache for event ID
Cache-->>API: Cache invalidated
API->>EventPublisher: Publish event deleted event
EventPublisher-->>API: Event published
API-->>U: Deleted event response
U->>API: GetCalendarEventsByDateRange(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch events by date range and user ID
DB-->>API: Events fetched
API->>Cache: Cache events by date range key
Cache-->>API: Cache updated
API->>EventPublisher: Publish events fetched event
EventPublisher-->>API: Event published
API-->>U: Events by date range response
U->>API: GetCalendarEventsByDateRangeWithAttendees(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch events by date range and user ID with attendees
DB-->>API: Events with attendees fetched
API->>Cache: Cache events with attendees by date range key
Cache-->>API: Cache updated
API->>EventPublisher: Publish events with attendees fetched event
EventPublisher-->>API: Event published
API-->>U: Events by date range with attendees response
U->>API: GetCalendarEventsByDateRangeWithAttendeesAndReminders(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch events by date range and user ID with attendees and reminders
DB-->>API: Events with attendees and reminders fetched
API->>Cache: Cache events with attendees and reminders by date range key
Cache-->>API: Cache updated
API->>EventPublisher: Publish events with attendees and reminders fetched event
EventPublisher-->>API: Event published
API-->>U: Events by date range with attendees and reminders response
U->>API: GetCalendarEventsByDateRangeWithAttendeesAndRemindersAndRecurrence(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch events by date range and user ID with attendees, reminders, and recurrence
DB-->>API: Events with attendees, reminders, and recurrence fetched
API->>Cache: Cache events with attendees, reminders, and recurrence by date range key
Cache-->>API: Cache updated
API->>EventPublisher: Publish events with attendees, reminders, and recurrence fetched event
EventPublisher-->>API: Event published
API-->>U: Events by date range with attendees, reminders, and recurrence response
U->>API: GetCalendarEventsByDateRangeWithAttendeesAndRemindersAndRecurrenceAndNotifications(request)
API->>Auth: Validate JWT
Auth-->>API: Authenticated User
API->>DB: Fetch events by date range and user ID with attendees, reminders, recurrence, and notifications
DB-->>API: Events with attendees, reminders, recurrence, and notifications fetched
API->>Cache: Cache events with attendees, reminders, recurrence, and notifications by date range key
Cache-->>API: Cache updated
API->>EventPublisher: Publish events with attendees, reminders, recurrence, and notifications fetched event
EventPublisher-->>API: Event published
API-->>U: Events by date range with attendees, reminders, recurrence, and notifications response
## 7. Caching Design
### Cache Strategy Selection
For calendar event scheduling, I'll use a **cache-aside pattern** with **LRU eviction policy**. This approach provides the best balance between performance and consistency for read-heavy workloads where events are frequently queried but updated less often.
### Cache Key Design
Cache keys will follow a namespace strategy to prevent key collisions and enable efficient invalidation:
```java
public class CacheKeyBuilder {
private static final String EVENT_NAMESPACE = "event:";
private static final String CALENDAR_NAMESPACE = "calendar:";
private static final String USER_EVENT_NAMESPACE = "user_event:";
public static String buildEventKey(String eventId) {
return EVENT_NAMESPACE + eventId;
}
public static String buildCalendarKey(String calendarId) {
return CALENDAR_NAMESPACE + calendarId;
}
public static String buildUserEventKey(String userId, String eventId) {
return USER_EVENT_NAMESPACE + userId + ":" + eventId;
}
}
TTL Strategy
| Data Type | TTL | Reason |
|---|---|---|
| Event details | 5 minutes | Frequent reads, low write frequency |
| Calendar metadata | 10 minutes | Less volatile than events |
| User event list | 2 minutes | High read frequency, recent updates |
| Calendar availability | 1 minute | Time-sensitive, frequent changes |
Cache Eviction Policy
Using LRU (Least Recently Used) with probabilistic early expiry to prevent cache stampede:
public class CacheManager {
private final RedisTemplate<String, Object> redisTemplate;
private static final int CACHE_STALE_THRESHOLD = 30; // seconds
public Object getCachedValue(String key, Supplier<Object> loader) {
String cacheKey = CacheKeyBuilder.buildEventKey(key);
Object cachedValue = redisTemplate.opsForValue().get(cacheKey);
if (cachedValue != null) {
// Check if cache is stale
Long lastAccess = getLastAccessTime(cacheKey);
if (System.currentTimeMillis() - lastAccess > CACHE_STALE_THRESHOLD * 1000) {
// Start async refresh
CompletableFuture.runAsync(() -> refreshCache(cacheKey, loader));
return cachedValue; // Serve stale data while refreshing
}
}
return loadAndCache(cacheKey, loader);
}
private void refreshCache(String key, Supplier<Object> loader) {
try {
Object value = loader.get();
redisTemplate.opsForValue().set(key, value, 5, TimeUnit.MINUTES);
} catch (Exception e) {
// Log error but don't fail the main operation
log.error("Failed to refresh cache for key: {}", key, e);
}
}
}
Cache Stampede Prevention
Implement probabilistic early expiry with distributed locks:
public class CacheStampedePrevention {
private static final String LOCK_PREFIX = "cache_lock:";
public Object getCachedWithLock(String key, Supplier<Object> loader) {
String lockKey = LOCK_PREFIX + key;
String lockValue = UUID.randomUUID().toString();
try {
// Try to acquire lock with timeout
if (redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS)) {
// Load fresh data
Object result = loader.get();
redisTemplate.opsForValue().set(key, result, 5, TimeUnit.MINUTES);
return result;
} else {
// Wait for lock to be released or timeout
Thread.sleep(100);
return redisTemplate.opsForValue().get(key);
}
} finally {
// Release lock if owned
releaseLock(lockKey, lockValue);
}
}
private void releaseLock(String lockKey, String lockValue) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey), lockValue);
}
}
Distributed Cache Architecture
Using Redis Cluster for high availability and horizontal scaling:
- Cluster size: 3 master nodes with 3 replica nodes each
- Sharding strategy: Hash-based consistent hashing for even distribution
- Replication: Async replication with automatic failover
- Persistence: RDB snapshots + AOF for durability
Cache Invalidation Strategy
Implement event-driven invalidation using a pub-sub model:
@Component
public class CacheInvalidator {
private final RedisTemplate<String, Object> redisTemplate;
private final ApplicationEventPublisher eventPublisher;
@EventListener
public void handleEventCreated(EventCreatedEvent event) {
// Invalidate related cache entries
String eventKey = CacheKeyBuilder.buildEventKey(event.getEventId());
String userEventKey = CacheKeyBuilder.buildUserEventKey(event.getUserId(), event.getEventId());
redisTemplate.delete(eventKey);
redisTemplate.delete(userEventKey);
// Broadcast invalidation to other services
eventPublisher.publishEvent(new CacheInvalidationEvent(event.getCalendarId()));
}
@EventListener
public void handleCalendarUpdated(CalendarUpdatedEvent event) {
// Invalidate all events in calendar
String calendarKey = CacheKeyBuilder.buildCalendarKey(event.getCalendarId());
redisTemplate.delete(calendarKey);
// Invalidate user event lists for calendar members
invalidateUserEventLists(event.getCalendarId());
}
}
Cache Hit/Miss Ratio Monitoring
@Component
public class CacheMetrics {
private final MeterRegistry meterRegistry;
public void recordCacheHit(String cacheName) {
Counter.builder("cache.hits")
.tag("name", cacheName)
.register(meterRegistry)
.increment();
}
public void recordCacheMiss(String cacheName) {
Counter.builder("cache.misses")
.tag("name", cacheName)
.register(meterRegistry)
.increment();
}
}
This caching design provides optimal read performance while maintaining data consistency through intelligent invalidation and stampede prevention mechanisms. The LRU eviction policy ensures that hot data remains in cache while stale entries are automatically evicted to make room for fresh data.
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 objects and persistence concerns. Each entity has its own repository interface that defines the contract for data operations.
public interface CalendarEventRepository {
CalendarEvent save(CalendarEvent event);
Optional<CalendarEvent> findById(UUID id);
List<CalendarEvent> findByUserIdAndTimeRange(UUID userId, Instant start, Instant end);
List<CalendarEvent> findByUserIdAndStatus(UUID userId, EventStatus status);
void deleteById(UUID id);
boolean existsByIdAndUserId(UUID id, UUID userId);
}
public interface CalendarEventParticipantRepository {
List<CalendarEventParticipant> findByEventId(UUID eventId);
void saveAll(List<CalendarEventParticipant> participants);
void deleteByEventId(UUID eventId);
}
ORM vs Raw Query Decisions
For the Calendar Event system, we use JPA/Hibernate as our ORM for most operations due to its strong typing, query optimization capabilities, and integration with Spring Data. However, for high-frequency read-heavy operations like time-range queries, we use raw SQL to achieve better performance and more precise control over query execution.
@Repository
public class CalendarEventRepositoryImpl implements CalendarEventRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<CalendarEvent> findByUserIdAndTimeRange(UUID userId, Instant start, Instant end) {
String jpql = """
SELECT e FROM CalendarEvent e
WHERE e.userId = :userId
AND e.startTime <= :endTime
AND e.endTime >= :startTime
""";
return entityManager.createQuery(jpql, CalendarEvent.class)
.setParameter("userId", userId)
.setParameter("startTime", start)
.setParameter("endTime", end)
.getResultList();
}
@Override
public List<CalendarEvent> findByUserIdAndStatus(UUID userId, EventStatus status) {
// Raw SQL for better performance on large datasets
String sql = """
SELECT * FROM calendar_events
WHERE user_id = ? AND status = ?
ORDER BY start_time
""";
return entityManager.createNativeQuery(sql, CalendarEvent.class)
.setParameter(1, userId)
.setParameter(2, status.name())
.getResultList();
}
}
Read vs Write Separation
We implement CQRS (Command Query Responsibility Segregation) for the calendar event system to separate read and write operations:
// Write-side repository - handles mutations
@Repository
public class CalendarEventWriteRepository {
@PersistenceContext
private EntityManager entityManager;
public CalendarEvent save(CalendarEvent event) {
if (event.getId() == null) {
entityManager.persist(event);
} else {
entityManager.merge(event);
}
return event;
}
public void deleteById(UUID id) {
entityManager.createQuery("DELETE FROM CalendarEvent e WHERE e.id = :id")
.setParameter("id", id)
.executeUpdate();
}
}
// Read-side repository - optimized for queries
@Repository
public class CalendarEventReadRepository {
@PersistenceContext
private EntityManager entityManager;
public List<CalendarEventProjection> findProjectedEvents(
UUID userId, Instant startTime, Instant endTime) {
String sql = """
SELECT
e.id,
e.title,
e.start_time,
e.end_time,
e.status,
e.created_at
FROM calendar_events e
WHERE e.user_id = :userId
AND e.start_time <= :endTime
AND e.end_time >= :startTime
ORDER BY e.start_time
""";
return entityManager.createNativeQuery(sql, "CalendarEventProjection")
.setParameter("userId", userId)
.setParameter("startTime", startTime)
.setParameter("endTime", endTime)
.getResultList();
}
}
Connection Pool Configuration
Connection pool is configured with HikariCP for optimal performance and resource management:
# application.yml
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
validation-timeout: 5000
leak-detection-threshold: 60000
N+1 Query Prevention Strategy
We implement eager loading and batch fetching to prevent N+1 query problems:
@Entity
@Table(name = "calendar_events")
@NamedEntityGraph(
name = "CalendarEvent.withParticipants",
attributeNodes = @NamedAttributeNode(value = "participants", fetchType = FetchType.EAGER)
)
public class CalendarEvent {
@Id
private UUID id;
@OneToMany(mappedBy = "event", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@BatchSize(size = 20)
private List<CalendarEventParticipant> participants = new ArrayList<>();
// ... other fields and methods
}
Batch Operation Design
Batch operations are implemented for bulk event creation and updates:
@Repository
public class BatchCalendarEventRepository {
@PersistenceContext
private EntityManager entityManager;
public void batchSave(List<CalendarEvent> events) {
int batchSize = 100;
for (int i = 0; i < events.size(); i += batchSize) {
int end = Math.min(i + batchSize, events.size());
List<CalendarEvent> batch = events.subList(i, end);
for (CalendarEvent event : batch) {
entityManager.persist(event);
}
entityManager.flush();
entityManager.clear();
}
}
public void batchUpdate(List<CalendarEvent> events) {
String sql = """
UPDATE calendar_events
SET title = :title,
start_time = :startTime,
end_time = :endTime,
updated_at = CURRENT_TIMESTAMP
WHERE id = :id
""";
for (CalendarEvent event : events) {
entityManager.createNativeQuery(sql)
.setParameter("id", event.getId())
.setParameter("title", event.getTitle())
.setParameter("startTime", event.getStartTime())
.setParameter("endTime", event.getEndTime())
.executeUpdate();
}
}
}
9. Asynchronous Processing & Messaging
Event-driven boundaries in this feature
The calendar event scheduler system will use a hybrid synchronous/asynchronous approach. The core scheduling and conflict resolution operations will be handled synchronously to ensure immediate consistency for critical business logic. However, certain downstream operations like notification delivery, email generation, and analytics tracking will be decoupled using asynchronous messaging.
Key triggers for async processing:
- Event creation/update/deletion → trigger notification and analytics events
- Calendar sharing → trigger invitation email generation
- Recurring event processing → schedule future occurrences asynchronously
Message schema design with sample payload
{
"version": "1.0",
"eventId": "uuid-1234567890",
"eventType": "EVENT_CREATED",
"timestamp": "2023-10-15T14:30:00Z",
"payload": {
"event": {
"id": "uuid-1234567890",
"title": "Team Meeting",
"startDateTime": "2023-10-15T15:00:00Z",
"endDateTime": "2023-10-15T16:00:00Z",
"organizer": {
"id": "user-123",
"email": "john.doe@example.com"
},
"attendees": [
{
"id": "user-456",
"email": "jane.smith@example.com"
}
]
}
},
"source": "calendar-service",
"correlationId": "trace-abc123"
}
Dead letter queue strategy and poison message handling
Poison messages will be handled through a dead-letter queue (DLQ) strategy with the following approach:
- Messages that fail more than 3 times will be moved to a DLQ topic
- Failed messages in DLQ will be logged with full context for manual inspection
- A separate monitoring system will alert on DLQ message volume and content
- Failed messages in DLQ can be replayed after root cause investigation
At-least-once vs. exactly-once delivery
The system will implement at-least-once delivery for most events due to the nature of calendar operations where duplicate notifications or analytics events are less problematic than missed ones. For critical operations like event creation, idempotency keys will be used to prevent duplicate processing.
Retry policy for consumers
Retry policy configuration:
- Max retries: 3 attempts
- Backoff strategy: Exponential with jitter (1s, 2s, 4s)
- Retryable errors: Network timeouts, service unavailability
- Non-retryable errors: Invalid payload, permission denied
Outbox pattern for reliable event publishing
The outbox pattern will be implemented to ensure event delivery reliability:
public class EventPublisher {
private final EventRepository eventRepository;
private final MessageBroker messageBroker;
public void publishEvent(Event event) {
// Store event in outbox table
OutboxEvent outboxEvent = new OutboxEvent(
UUID.randomUUID(),
"EVENT_CREATED",
event,
EventStatus.PENDING
);
// Transactionally persist outbox event and actual event
eventRepository.save(outboxEvent);
eventRepository.save(event);
// Publish to message queue
messageBroker.publish(outboxEvent);
}
}
Sequence diagram for async flows
sequenceDiagram
participant Service
participant OutboxRepo
participant MessageBroker
participant Consumer
Service->>OutboxRepo: Store event in outbox table
OutboxRepo-->>Service: Acknowledge write
Service->>MessageBroker: Publish to queue
MessageBroker-->>Service: Acknowledge publish
MessageBroker->>Consumer: Deliver event
Consumer->>Consumer: Process and acknowledge
10. Error Handling Design
Error Classification Hierarchy
The system implements a three-tier error classification strategy:
- Domain Errors - Business logic violations (e.g., overlapping events, invalid recurrence rules)
- Infrastructure Errors - System-level issues (e.g., database connection failures, cache unavailability)
- Validation Errors - Input format and constraint violations (e.g., malformed date, missing required fields)
public abstract class BaseException extends RuntimeException {
private final String errorCode;
private final int httpStatus;
public BaseException(String errorCode, int httpStatus, String message) {
super(message);
this.errorCode = errorCode;
this.httpStatus = httpStatus;
}
// Getters
}
public class ValidationException extends BaseException {
public ValidationException(String message) {
super("VALIDATION_ERROR", 400, message);
}
}
public class DomainException extends BaseException {
public DomainException(String errorCode, String message) {
super(errorCode, 400, message);
}
}
public class InfrastructureException extends BaseException {
public InfrastructureException(String errorCode, String message) {
super(errorCode, 500, message);
}
}
Error Propagation Strategy
Errors are handled at their point of occurrence but propagated up the call stack as appropriate:
- Local handling: Validation errors are caught and converted to proper HTTP responses
- Service boundary: Business logic exceptions are re-thrown to allow API layer to map them to appropriate HTTP status codes
- Infrastructure errors: Wrapped in infrastructure exceptions and logged for monitoring
Retry Strategy with Exponential Backoff and Jitter
For transient infrastructure issues, the system implements a robust retry mechanism:
public class RetryableOperation {
private static final int MAX_RETRIES = 3;
private static final long BASE_DELAY_MS = 1000;
private static final double JITTER_RANGE = 0.5;
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)
* (1 + JITTER_RANGE * (Math.random() - 0.5)));
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new InfrastructureException("Retry interrupted", "RETRY_INTERRUPTED", ie);
}
attempts++;
}
}
throw new InfrastructureException("Max retries exceeded", "MAX_RETRIES_EXCEEDED");
}
}
Circuit Breaker Design
The system uses a circuit breaker pattern for external dependencies like notification services:
public class CircuitBreaker {
private final int failureThreshold;
private final long timeoutMs;
private final int successThreshold;
private volatile CircuitState state = CircuitState.CLOSED;
private volatile int failureCount = 0;
private volatile long lastFailureTime = 0;
public enum CircuitState {
CLOSED, OPEN, HALF_OPEN
}
public <T> T execute(Supplier<T> operation) {
switch (state) {
case CLOSED:
return executeClosed(operation);
case OPEN:
return executeOpen(operation);
case HALF_OPEN:
return executeHalfOpen(operation);
}
throw new IllegalStateException("Invalid circuit state");
}
private <T> T executeClosed(Supplier<T> operation) {
try {
T result = operation.get();
failureCount = 0; // Reset on success
return result;
} catch (Exception e) {
failureCount++;
lastFailureTime = System.currentTimeMillis();
if (failureCount >= failureThreshold) {
state = CircuitState.OPEN;
}
throw e;
}
}
private <T> T executeOpen(Supplier<T> operation) {
if (System.currentTimeMillis() - lastFailureTime > timeoutMs) {
state = CircuitState.HALF_OPEN;
return executeHalfOpen(operation);
}
throw new InfrastructureException("Circuit breaker is open", "CIRCUIT_OPEN");
}
private <T> T executeHalfOpen(Supplier<T> operation) {
try {
T result = operation.get();
state = CircuitState.CLOSED;
return result;
} catch (Exception e) {
state = CircuitState.OPEN;
throw e;
}
}
}
Fallback Behavior Design
For non-critical operations, fallbacks are implemented:
public class NotificationFallback {
public void sendNotificationWithFallback(String userId, String message) {
try {
notificationService.send(userId, message);
} catch (InfrastructureException e) {
// Fallback to email or log error for manual follow-up
logger.warn("Failed to send notification to user {}, falling back to email", userId, e);
emailService.send(userId, "Calendar event update: " + message);
}
}
}
Error Logging Format and Alerting Thresholds
Structured logging is implemented with consistent fields:
public class ErrorLogger {
private static final Logger logger = LoggerFactory.getLogger(ErrorLogger.class);
public void logError(String operation, String userId, String entityId,
String errorCode, Exception exception) {
Map<String, Object> logFields = new HashMap<>();
logFields.put("timestamp", Instant.now());
logFields.put("operation", operation);
logFields.put("userId", userId);
logFields.put("entityId", entityId);
logFields.put("errorCode", errorCode);
logFields.put("errorMessage", exception.getMessage());
logFields.put("stackTrace", exception.getStackTrace().toString());
logger.error("Calendar service error occurred", logFields);
}
}
Alerting thresholds:
| Error Type | Alert Threshold | Severity |
|---|---|---|
| Infrastructure errors (DB, Cache) | > 5 per minute | Critical |
| Validation errors | > 100 per hour | Warning |
| Domain errors (overlapping events) | > 50 per hour | Info |
Error Response Format
Standardized JSON error responses:
{
"error": {
"code": "OVERLAPPING_EVENTS",
"message": "Event overlaps with existing event",
"details": {
"conflictingEventId": "event-123",
"conflictStart": "2023-12-01T10:00:00Z",
"conflictEnd": "2023-12-01T11:00:00Z"
}
},
"timestamp": "2023-12-01T09:30:00Z",
"requestId": "req-456"
}
11. Security Design
Input Validation and Sanitization Strategy
All API inputs undergo strict validation using Apache Commons Validator and custom regex patterns for:
- Calendar event titles: 1-255 alphanumeric + space + punctuation characters
- Description fields: Max 10000 chars with HTML sanitization using OWASP Java HTML Sanitizer
- Date/time fields: ISO 8601 format with timezone validation
- Recurrence patterns: Whitelisted values (DAILY, WEEKLY, MONTHLY, YEARLY)
SQL Injection Prevention
All database queries use parameterized prepared statements with:
- MyBatis for ORM mapping to prevent dynamic query construction
- Input validation layer that strips dangerous SQL keywords (SELECT, INSERT, UPDATE, DELETE)
- Query whitelisting for all date range and sorting operations
Authentication Middleware Design
JWT-based authentication with:
- 24-hour access tokens with 7-day refresh tokens
- Token validation middleware that checks signature, expiration, and audience claims
- Session management with Redis-based token blacklisting for logout/revocation
- OAuth2 integration with Google Calendar API for calendar sync
Authorization Enforcement
Role-based access control (RBAC) implemented via:
- User roles: ADMIN, ORGANIZER, PARTICIPANT
- Resource-based permissions:
- ORGANIZER can CRUD events in their calendar
- PARTICIPANT can view and RSVP to events they're invited to
- Permission checking at service layer using Spring Security annotations:
@PreAuthorize("hasRole('ORGANIZER') and @calendarPermissionService.canAccessCalendar(#calendarId)")
Sensitive Data Encryption
- At rest: AES-256 encryption with AWS KMS for calendar data and user preferences
- In transit: TLS 1.3 with HSTS header enforcement (max-age=31536000)
- PII handling: Calendar event descriptions and participant emails are encrypted at rest using AES-256-GCM
PII Handling and Masking Strategy
- User email addresses are stored in encrypted format (AES-256) with salted SHA-256 hash for lookups
- Calendar event descriptions are encrypted before storage and decrypted on read
- Email addresses in notifications are masked (e.g., "j***@example.com") for display purposes
Secrets Management
- AWS Secrets Manager integration for:
- Database credentials (rotated every 90 days)
- OAuth2 client secrets
- JWT signing keys (rotated every 30 days)
- Secrets are rotated using AWS Lambda functions with proper error handling and fallback mechanisms
Audit Logging Design
- All security-relevant operations logged with:
- Timestamp (UTC)
- User ID (hashed for privacy)
- Operation type (login, event_create, event_update, etc.)
- IP address (masked to /24)
- Session ID (hashed)
- Action result (success/failure)
- Log retention: 90 days for audit logs, 30 days for security events
- Logs are sent to centralized logging system (ELK stack) with:
- Structured JSON format
- PII masking for email addresses and names
- Alerting thresholds for failed login attempts (5+ in 1 hour)
12. Testing Strategy
Unit Testing Scope and Coverage Targets
- Target coverage: 85% line coverage, 90% branch coverage for core business logic
- Scope: Service layer methods, domain object validations, utility functions, and core algorithms
- Key areas to test:
- Event creation with overlapping time constraints
- Recurrence rule parsing and validation
- Calendar availability calculation logic
- Conflict detection algorithms
- Timezone conversion and handling
- User permission validation logic
Integration Testing Strategy
- Database integration: Use testcontainers with PostgreSQL 13+ for real DB testing (schema, transactions, constraints)
- External service mocks: WireMock or Mockito for calendar sync providers (Google Calendar, Outlook)
- Cross-service dependencies: Test with embedded Redis and Kafka mock instances for caching and messaging layers
- End-to-end flows: Integration tests for event creation → notification → calendar sync workflows
Contract Testing
- Framework: Pact for consumer-driven contract testing between calendar service and external providers
- Scope: API contracts for calendar sync endpoints, webhook payload formats, and event notification schemas
- Versioning: Contract versioning aligned with API versioning strategy to prevent breaking changes
Performance Testing Tool and Key Scenarios
- Tool: k6 for load testing with realistic concurrent user scenarios
- Key test scenarios:
- Concurrent event creation (500+ concurrent users)
- High-volume recurrence rule processing (10k events with daily recurrence)
- Calendar availability query under load (500+ concurrent queries)
- Batch event import operations (1000+ events in single request)
- Target SLIs: 95th percentile response time < 200ms for CRUD operations, 99th percentile < 500ms
Test Data Management Strategy
- Fixtures: YAML-based fixtures for standard calendar structures, user preferences, and default event templates
- Factories: Java-based factory classes for generating test data with realistic time ranges and recurrence patterns
- Seeding: Database seeding scripts for test environments, including sample users, calendars, and recurring events
- Isolation: Per-test database transactions with clean state rollback to ensure test independence
// Example unit test for event conflict detection logic
@Test
public void testEventCreationWithConflictingTime() {
// Given
Event existingEvent = createEvent("Meeting",
LocalDateTime.of(2023, 10, 15, 10, 0),
LocalDateTime.of(2023, 10, 15, 11, 0));
Event newEvent = createEvent("Conference",
LocalDateTime.of(2023, 10, 15, 10, 30),
LocalDateTime.of(2023, 10, 15, 12, 0));
// When
ConflictResult result = calendarService.checkEventConflicts(newEvent, existingEvent);
// Then
assertTrue(result.hasConflicts());
assertEquals(ConflictType.TIME_OVERLAP, result.getConflictType());
}
13. Observability Design
Structured Logging Schema
All logs must follow a consistent schema to enable effective search, filtering, and correlation:
{
"timestamp": "2023-10-27T14:30:45.123Z",
"level": "INFO",
"service": "calendar-service",
"trace_id": "12345678-1234-1234-1234-123456789012",
"span_id": "abcdef12-3456-7890-abcd-ef1234567890",
"user_id": "user_12345",
"entity_id": "event_98765",
"operation": "createEvent",
"request_id": "req_abcde12345",
"message": "Event created successfully",
"duration_ms": 125,
"error_code": null,
"error_message": null,
"stack_trace": null,
"tags": {
"event_type": "meeting",
"calendar_id": "cal_54321"
}
}
PII masking rules:
- User email addresses are masked as
user***@domain.com - Full names are truncated to first character:
J***n Doe - Phone numbers are masked as
+1 (555) ***-****
Distributed Tracing Instrumentation
OpenTelemetry is used for distributed tracing with custom spans:
Key Operations to Trace:
calendar.event.create- span for event creation flowcalendar.event.update- span for event update flowcalendar.event.conflict.check- span for conflict detectioncalendar.event.notification.send- span for notification publishing
Span Attributes:
// Example in Java using OpenTelemetry SDK
Span span = tracer.spanBuilder("calendar.event.create")
.setAttribute("event.id", event.getId())
.setAttribute("user.id", userId)
.setAttribute("calendar.id", calendarId)
.setAttribute("event.duration.minutes", durationMinutes)
.startSpan();
Metrics Design
RED Method Metrics (Rate, Errors, Duration):
| Metric Name | Type | Labels | Description |
|---|---|---|---|
calendar.event.created | Counter | event_type, user_role | Total events created |
calendar.event.update.failed | Counter | error_code | Failed event updates |
calendar.event.conflict.duration | Histogram | event_type | Conflict check latency |
calendar.event.notification.sent | Counter | notification_type, status | Notification send success/failure |
calendar.event.processing.duration | Histogram | operation, status | End-to-end event processing time |
calendar.event.concurrent.requests | Gauge | operation | Current concurrent request count |
Example Histogram Configuration:
// Java Micrometer metrics example
private final Timer processingTimer = Timer.builder("calendar.event.processing.duration")
.tag("operation", "create")
.tag("status", "success")
.register(meterRegistry);
private final Counter conflictCounter = Counter.builder("calendar.event.conflict.check")
.tag("event_type", "meeting")
.register(meterRegistry);
Health Check Endpoint Design
Endpoints with their checks:
| Endpoint | Check Performed | Expected Response |
|---|---|---|
/health | Service readiness, DB connectivity, cache availability | {"status":"healthy","details":{"db":"ok","cache":"ok"}} |
/ready | All dependencies ready, DB migrations applied, queue consumers healthy | {"status":"ready","components":{"db":"healthy","message_queue":"healthy"}} |
/live | Process is alive, memory usage under threshold | {"status":"alive","uptime_seconds":3600} |
/startup | Service has completed startup sequence | {"status":"complete","startup_time_ms":1250} |
Alerting Thresholds
Critical Alerts (require on-call escalation):
| Metric | Threshold | Alert Level | Escalation |
|---|---|---|---|
calendar.event.update.failed rate (5m) | > 10/minute | Critical | On-call engineer |
calendar.event.processing.duration p99 | > 5000ms | Critical | On-call engineer |
calendar.event.conflict.duration p99 | > 1000ms | Warning | Team alerting |
calendar.event.notification.sent rate (1h) | < 50% success | Warning | Team alerting |
Warning Alerts (team notification):
| Metric | Threshold | Alert Level |
|---|---|---|
calendar.event.processing.duration p95 | > 2000ms | Warning |
calendar.event.concurrent.requests | > 1000 concurrent | Warning |
calendar.event.conflict.check rate (5m) | > 100/minute | Warning |
Alerting Configuration Example:
# Prometheus alert rules example
groups:
- name: calendar-service-alerts
rules:
- alert: CalendarEventUpdateFailureRate
expr: rate(calendar_event_update_failed[5m]) > 0.1
for: 2m
labels:
severity: critical
annotations:
summary: "High event update failure rate"
description: "Event updates failing at {{ value }} per minute"
14. Open Questions, Risks & Trade-offs
Decisions requiring product / engineering alignment
- Timezone handling strategy: Should we store all times in UTC and convert to user timezone at display, or should we store user's local time with timezone offset? This decision affects data model complexity and API surface.
- Event recurrence pattern complexity: How granular should recurrence patterns be? Should we support complex patterns like "last Monday of month" or keep it simple with basic daily/weekly/monthly patterns?
- Resource booking vs. time slot allocation: Should we support multiple resource types (meeting rooms, equipment) or keep it simple with single-user time slots?
- Notification delivery strategy: Should we implement push notifications, email, or SMS, and how should we handle notification failures?
Known trade-offs and justifications
| Trade-off | Option 1 | Option 2 | Justification |
|---|---|---|---|
| Event Duration vs. Recurrence | Fixed duration events | Flexible duration with recurrence | Fixed duration simplifies scheduling logic and reduces complexity in conflict detection, but limits flexibility for recurring meetings |
| Database Schema vs. Denormalization | Normalized schema with joins | Denormalized for performance | Normalized provides data consistency and reduces storage overhead, but denormalization improves read performance for common queries |
| Real-time vs. Batch Updates | Real-time updates via WebSockets | Batch updates with polling | WebSocket provides better UX but increases infrastructure complexity and memory usage |
| Event Ownership Model | Single owner per event | Multiple owners with permissions | Single owner simplifies access control but limits collaboration features |
Risks and mitigation strategies
Risk: Calendar conflicts and scheduling collisions
- Mitigation: Implement optimistic locking with conflict detection, provide user-friendly conflict resolution UI, and maintain a global event conflict cache
Risk: High concurrent event creation/deletion
- Mitigation: Use distributed locks for critical sections, implement batch operations for bulk scheduling, and utilize read-replicas for high-volume reads
Risk: Data consistency in distributed systems
- Mitigation: Implement Saga pattern for multi-step operations, use outbox pattern for reliable event publishing, and maintain strong consistency at the transaction boundary
Risk: Performance degradation under high load
- Mitigation: Implement comprehensive caching strategy, use database connection pooling, and design with horizontal scaling in mind
Technical debt acknowledged
- Event recurrence engine complexity: The current design assumes simple recurrence patterns, but complex patterns like "last Friday of the month" will require significant refactoring and additional computational overhead
- Timezone handling limitations: The current implementation assumes all times are in UTC, but proper timezone support requires extensive testing and handling of edge cases like DST transitions
- Notification system limitations: The current design doesn't account for notification delivery failures or retry mechanisms, which could lead to missed notifications in production
- Event search and filtering complexity: As the system scales, complex search queries (e.g., "all events in the next 30 days with specific attendees") may require additional indexing strategies and potentially introduce performance bottlenecks