Building Resilient Reservation Systems: State Management & Race Conditions
Introduction
In security-critical systems like smart lock management, preventing double-booking and maintaining consistent state isn't just about user experience—it's about physical security. When multiple users attempt to reserve the same resource simultaneously, robust state management becomes essential to prevent conflicts that could compromise access control.
This article explores advanced patterns for handling reservation conflicts, implementing proper RBAC guards, and building resilient payment flows in distributed systems.
Preventing Double-Booking Through Centralized Intent Management
One of the most critical aspects of reservation systems is preventing race conditions that could lead to double-booking. A centralized intent overlap detection system provides a robust solution:
interface ReservationIntent {
id: string;
resourceId: string;
timeSlot: TimeRange;
userId: string;
status: 'pending' | 'confirmed' | 'cancelled';
createdAt: Date;
}
class ReservationManager {
async preventOverlapConflict(
intent: ReservationIntent,
skipOverlapTrigger: boolean = false
): Promise<void> {
if (skipOverlapTrigger) {
return; // Allow bypass for specific scenarios
}
const overlappingIntents = await this.findOverlappingIntents(
intent.resourceId,
intent.timeSlot
);
if (overlappingIntents.length > 0) {
throw new ConflictError({
code: 'RESERVATION_OVERLAP',
message: 'Resource already reserved for this time slot',
conflictingIntents: overlappingIntents.map(i => i.id)
});
}
}
private async findOverlappingIntents(
resourceId: string,
timeSlot: TimeRange
): Promise<ReservationIntent[]> {
return this.intentRepository.findWhere({
resourceId,
status: ['pending', 'confirmed'],
timeSlot: {
overlaps: timeSlot
}
});
}
}This approach provides several security benefits:
- Atomic conflict detection: Prevents race conditions at the database level
- Audit trail: All reservation attempts are logged with intent records
- Graceful degradation: Controlled bypass for specific scenarios
Day-of-Week Plan Restrictions: Temporal Access Control
Implementing time-based access restrictions adds another layer of security control. This pattern is particularly valuable for facilities that need different access policies based on schedules:
interface PlanAvailability {
availableDaysOfWeek: number[]; // 0=Sunday, 1=Monday, etc.
availableOnHoliday: boolean;
}
class TemporalAccessValidator {
validatePlanAvailability(
plan: PlanAvailability,
requestedDate: Date,
isHoliday: boolean
): ValidationResult {
// Holiday takes precedence over day-of-week rules
if (isHoliday) {
if (!plan.availableOnHoliday) {
return {
isValid: false,
error: 'Plan not available on holidays',
code: 'HOLIDAY_RESTRICTED'
};
}
return { isValid: true };
}
const dayOfWeek = requestedDate.getDay();
if (!plan.availableDaysOfWeek.includes(dayOfWeek)) {
return {
isValid: false,
error: 'Plan not available on this day of week',
code: 'DAY_RESTRICTED'
};
}
return { isValid: true };
}
// Prevent multidimensional arrays that could bypass validation
validateAvailableDays(days: unknown): number[] {
if (!Array.isArray(days)) {
throw new ValidationError('Available days must be an array');
}
// Reject nested arrays
if (days.some(day => Array.isArray(day))) {
throw new ValidationError('Multidimensional arrays not allowed');
}
return days.filter(day =>
Number.isInteger(day) && day >= 0 && day <= 6
);
}
}Role-Based Access Control (RBAC) Route Guards
Proper RBAC implementation requires enforcement at multiple layers. Route-level guards provide the first line of defense:
interface UserRole {
name: string;
permissions: string[];
routes: string[];
}
class RBACRouteGuard {
private rolePermissions: Map<string, UserRole> = new Map();
enforceRouteAccess(userRole: string, requestedRoute: string): boolean {
const role = this.rolePermissions.get(userRole);
if (!role) {
throw new AuthorizationError('Invalid role');
}
// Exact route match
if (role.routes.includes(requestedRoute)) {
return true;
}
// Pattern-based matching for dynamic routes
const hasPatternMatch = role.routes.some(pattern => {
const regex = new RegExp(pattern.replace('*', '.*'));
return regex.test(requestedRoute);
});
if (!hasPatternMatch) {
throw new AuthorizationError({
code: 'ROUTE_ACCESS_DENIED',
message: `Role '${userRole}' cannot access route '${requestedRoute}'`,
userRole,
requestedRoute
});
}
return true;
}
// Facility-scoped override pattern for hierarchical permissions
checkFacilityOverride(
userRole: string,
facilityId: string,
action: string
): boolean {
const overrideKey = `${facilityId}:${action}`;
const role = this.rolePermissions.get(userRole);
return role?.permissions.includes(overrideKey) ?? false;
}
}Resilient Payment Recovery Patterns
Payment failures in reservation systems require sophisticated recovery mechanisms to handle edge cases where charges succeed but confirmations fail:
interface PaymentIntent {
id: string;
reservationId: string;
amount: number;
status: 'requires_payment' | 'processing' | 'succeeded' | 'requires_action';
stripeCustomerId?: string;
}
class PaymentRecoveryManager {
async recoverChargedButUnconfirmed(): Promise<void> {
// Find reservations where payment succeeded but reservation wasn't confirmed
const orphanedPayments = await this.findOrphanedSuccessfulPayments();
for (const payment of orphanedPayments) {
try {
await this.finalizeReservation(payment.reservationId);
await this.logRecoveryAction(payment.id, 'auto_recovered');
} catch (error) {
await this.handleRecoveryFailure(payment, error);
}
}
}
async resumePaymentFlow(
reservationId: string,
customerId?: string
): Promise<PaymentIntent> {
const reservation = await this.getIncompleteReservation(reservationId);
// Attach customer to PaymentIntent for saved card functionality
const paymentIntent = await this.stripeClient.paymentIntents.update(
reservation.paymentIntentId,
{
customer: customerId,
// Enable Link and saved card features
payment_method_options: {
card: {
setup_future_usage: 'off_session'
}
}
}
);
// Update local state to reflect resumed flow
await this.updateReservationStatus(reservationId, 'payment_resumed');
return paymentIntent;
}
private async handleRecoveryFailure(
payment: PaymentIntent,
error: Error
): Promise<void> {
await this.auditLogger.error({
event: 'payment_recovery_failed',
paymentId: payment.id,
reservationId: payment.reservationId,
error: error.message,
timestamp: new Date().toISOString()
});
// Escalate to manual review
await this.escalateToManualReview(payment, error);
}
}Feature Flag Management for Gradual Rollouts
Security-critical features require careful rollout strategies. Feature flags enable controlled deployment while maintaining system stability:
class FeatureRolloutManager {
private facilityFeatures: Map<string, Set<string>> = new Map();
enableFeatureForFacilities(
feature: string,
facilities: string[],
validationRules?: ValidationRule[]
): void {
// Validate feature rollout before enabling
if (validationRules) {
for (const rule of validationRules) {
if (!rule.validate(facilities)) {
throw new ValidationError(rule.message);
}
}
}
facilities.forEach(facilityId => {
if (!this.facilityFeatures.has(facilityId)) {
this.facilityFeatures.set(facilityId, new Set());
}
this.facilityFeatures.get(facilityId)!.add(feature);
});
this.auditFeatureChange('enable', feature, facilities);
}
isFeatureEnabled(facilityId: string, feature: string): boolean {
const features = this.facilityFeatures.get(facilityId);
return features?.has(feature) ?? false;
}
private auditFeatureChange(
action: 'enable' | 'disable',
feature: string,
facilities: string[]
): void {
this.auditLogger.info({
event: 'feature_flag_change',
action,
feature,
facilities,
timestamp: new Date().toISOString()
});
}
}Summary
Building resilient reservation systems requires attention to multiple layers of security and reliability:
- Conflict Prevention: Centralized intent management prevents race conditions that could compromise resource allocation
- Temporal Controls: Day-of-week and holiday restrictions provide fine-grained access control
- RBAC Guards: Multi-layer authorization prevents unauthorized access to sensitive operations
- Payment Recovery: Sophisticated error handling ensures no lost transactions in payment flows
- Gradual Rollouts: Feature flags enable safe deployment of security-critical features
These patterns demonstrate how robust state management and comprehensive error handling create trustworthy systems that can handle edge cases gracefully while maintaining security integrity.