Architecture Overview
System Overview
The Pricing Engine is a high-performance, API-only Rails 8 application for dynamic pricing rule management. It provides Partner API for CRUD operations and Internal API for price calculations.
Architecture Diagram
┌─────────────────────────────────────────────────────────────────────────────┐
│ CLIENTS │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │Partner Portal│ │Internal Svc │ │ Admin Tools │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
└─────────┼────────────────┼────────────────┼─────────────────────────────────┘
│ │ │
└────────────────┴────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ RAILS 8 API APPLICATION │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ RACK MIDDLEWARE │ │
│ │ Rack::Attack (Rate Limit) │ Rack::CORS │ Lograge (Logging) │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ API CONTROLLERS │ │
│ │ ┌────────────────────────┐ ┌────────────────────────────────────┐ │ │
│ │ │ Partner API (v1) │ │ Internal API (v1) │ │ │
│ │ │ • PricingRulesCtrl │ │ • PricesController (calculate) │ │ │
│ │ │ - CRUD operations │ │ • BulkCalculateController │ │ │
│ │ │ - Clone / Toggle │ │ • SimulateController │ │ │
│ │ └────────────────────────┘ └────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ SERVICE LAYER │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │ │
│ │ │ Pricing::Engine │ │ RuleMatcher │ │ ConditionEvaluator │ │ │
│ │ │ (Orchestrator) │ │ (Find Rules) │ │ (Match Conditions) │ │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────────┘ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │ │
│ │ │ PriceCalculator │ │ PriorityResolver│ │GlobalLimitsEnforcer │ │ │
│ │ │ (Apply Values) │ │ (Sort/Filter) │ │ (Cap Adjustments) │ │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ DATA LAYER │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │ │
│ │ │ PricingRule │ │DateTimeCondition│ │ ConditionItem │ │ │
│ │ │ (Parent) │ │ (Condition) │ │ (Time Slots) │ │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────────┘ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │PricingRules │ │ Serializers │ │ │
│ │ │Packages (Join) │ │ (JSON output) │ │ │
│ │ └─────────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────┐
│ POSTGRESQL │
│ • pricing_rules │
│ • date_time_conds │
│ • condition_items │
│ • pricing_packages │
└─────────────────────┘
Technology Stack
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| Runtime | Ruby + YJIT | 3.3.6 | High-performance execution |
| Framework | Rails (API mode) | 8.0.0 | Web application framework |
| Database | PostgreSQL | 14+ | Primary data store |
| JSON | Oj | - | Fast JSON serialization |
| HTTP Server | Puma | 6.x | Multi-threaded web server |
| Rate Limiting | Rack::Attack | - | API protection |
| API Docs | Rswag | - | OpenAPI/Swagger generation |
| Testing | RSpec | - | Test framework |
Service Layer
All business logic is encapsulated in service objects under app/services/pricing/:
Core Services
| Service | Responsibility |
|---|---|
Engine | Main orchestrator - coordinates all pricing calculations |
RuleMatcher | Finds applicable rules for a restaurant/package |
ConditionEvaluator | Evaluates if conditions match target datetime |
PriceCalculator | Applies price adjustments (increase/decrease) |
PriorityResolver | Determines which rules take precedence |
GlobalLimitsEnforcer | Caps adjustments within allowed limits |
Service Pattern
All services inherit from BaseService and follow a consistent pattern:
module Pricing
class Engine < BaseService
def initialize(restaurant_id:, original_price:, target_datetime:, **options)
@restaurant_id = restaurant_id
@original_price = original_price
@target_datetime = target_datetime
@options = options
end
def call
# 1. Validate inputs
# 2. Find matching rules
# 3. Calculate adjustments
# 4. Apply global limits
# 5. Return result
success(data: result_hash)
rescue StandardError => e
failure(error: e.message)
end
end
end
Directory Structure
app/
├── controllers/
│ ├── application_controller.rb
│ ├── health_controller.rb
│ ├── api/
│ │ ├── partner/
│ │ │ └── v1/
│ │ │ ├── base_controller.rb
│ │ │ └── pricing_rules_controller.rb
│ │ └── internal/
│ │ └── v1/
│ │ ├── base_controller.rb
│ │ └── prices_controller.rb
│ └── concerns/
│ └── error_handler.rb
├── models/
│ ├── pricing_rule.rb
│ ├── date_time_condition.rb
│ ├── condition_item.rb
│ └── pricing_rules_package.rb
├── serializers/
│ ├── pricing_rule_serializer.rb
│ ├── date_time_condition_serializer.rb
│ └── condition_item_serializer.rb
├── services/
│ ├── base_service.rb
│ └── pricing/
│ ├── engine.rb
│ ├── rule_matcher.rb
│ ├── condition_evaluator.rb
│ ├── price_calculator.rb
│ ├── priority_resolver.rb
│ └── global_limits_enforcer.rb
└── repositories/
└── pricing_rule_repository.rb
Data Flow: Price Calculation
┌─────────────────────────────────────────────────────────────────┐
│ 1. Request arrives at Internal API │
│ POST /api/internal/v1/prices/calculate │
│ { restaurant_id, original_price, target_datetime } │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. Pricing::Engine orchestrates │
│ • Validates inputs │
│ • Calls RuleMatcher to find applicable rules │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. RuleMatcher finds rules │
│ • Filter by restaurant_id │
│ • Filter by is_active = true │
│ • Filter by valid_from/valid_until │
│ • Filter by package_id (if provided) │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. ConditionEvaluator checks each rule's conditions │
│ • Match date_type against target_datetime │
│ • Check days_of_week (if applicable) │
│ • Check condition_items date/time ranges │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 5. PriorityResolver determines which rules apply │
│ • highest_priority: Only top-priority rule │
│ • stack_all: All matching rules in priority order │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 6. PriceCalculator applies adjustments │
│ • percentage: (price * value / 10000) │
│ • fixed: ± value (in cents) │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 7. GlobalLimitsEnforcer caps result (if limits provided) │
│ • max_increase_percentage │
│ • max_decrease_percentage │
│ • min_price / max_price │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 8. Return result │
│ { │
│ original_price: 10000, # $100.00 │
│ final_price: 11500, # $115.00 │
│ total_adjustment: 1500, # $15.00 │
│ applied_rules: [...] │
│ } │
└─────────────────────────────────────────────────────────────────┘
Key Design Principles
1. Service Objects
- Single responsibility per service
- Dependency injection via constructor
- Consistent return type (Result object)
2. Integer Cents
- All monetary values stored as integers
- Eliminates floating-point precision issues
- Industry standard (Stripe, etc.)
3. Extensibility
- Rule types are pluggable
- New condition types can be added without schema changes
- Strategy pattern for calculation strategies
4. Performance
- YJIT enabled for 15-25% speedup
- Efficient database queries with proper indexes
- Eager loading to prevent N+1 queries