DEV STAGING PROD

System Overview

OptimalHR is a multi-tenant Human Capital Management (HCM) SaaS platform built on AWS serverless architecture. It serves multiple customer organizations through a shared pool model with IAM-enforced data isolation at the DynamoDB level.

🖥 HR Admin — Vue.js 3 📱 ESS Mobile — React Native 0.69.6 🔧 API Client — Postman
🛡 Custom Lambda Authorizer 📜 Amazon Verified Permissions (AVP) Allow → Forward to service 🚫 Deny → 403 Forbidden
👥 Cognito User Pools (per tenant) 🆔 Cognito Identity Pools (per tenant) 🔑 Bearer JWT (standard + custom claims) Pre-Auth Lambda (env detection) 🏷 Pre-Token Lambda (custom claims)
Employment PIM MeAtWork (ESS) Work Schedule Leave Time & Attendance Overtime Payroll Notifications IAM SRM
🗃 DynamoDB (tenant data, pool model) 📦 S3 (objects & documents)
EventBridge Schedules (per-env) 🔨 JobPicker Lambda Job Runner Lambdas

Request Flow

The lifecycle of an API request from a client application through authentication, authorization and finally the backend service.

🖥
Client App
HR Admin / ESS Mobile
🔑
JWT Token
Bearer auth header
🛡
API Gateway
REST API endpoint
📜
Lambda Authorizer
JWT validation
AVP Policy Check
Tenant policy store
Microservice
Express.js on Lambda
🗃
DynamoDB
Tenant-scoped data
← Scroll horizontally if needed →
🛈
Deny Path: If the AVP policy check returns Deny, API Gateway immediately returns a 403 Forbidden response to the caller. The request never reaches the backend microservice.

Multi-Tenancy Model

Pool model with shared DynamoDB tables. Data isolation enforced at the IAM policy level using TenantId as the partition key.

System Admin Tenant

Platform owner. Cross-tenant visibility and operations.

  • Cognito User Pool (system)
  • Cognito Identity Pool (system)
  • SystemAdminRole + SystemAdminPolicy
  • SystemSuperuserRole
  • SystemSupportRole + SystemSupportPolicy
🔒
Scoped across all customer tenants

Customer Tenant

Individual customer organization. Isolated to own data.

  • Cognito User Pool (own)
  • Cognito Identity Pool (own)
  • TenantAdminRole + TenantAdminPolicy
  • TenantUserRole + TenantUserPolicy
🔒
Scoped to own tenant only

Data Isolation — Pool Model

All tenants share the same DynamoDB tables. Row-level isolation is enforced by IAM policies that restrict access to items matching the authenticated tenant's TenantId.

Partition Key (PK)
TenantId
Sort Key (SK)
EntityItem
TenantId (PK)EntityItem (SK)Example
TENANT#acmeEMP#001Employee record
TENANT#acmeLEAVE#2024-001Leave request
TENANT#globexEMP#042Employee record (different tenant)
TENANT#globexPAY#2024-03Payroll run
🛡
IAM Policy Condition: Each tenant's IAM policy includes a dynamodb:LeadingKeys condition restricting operations to rows where PK matches their TenantId. System admin roles omit this condition for cross-tenant access.

🔒 IAM Model

AWS IAM roles and policies provisioned per tenant type.

Tenant TypeIAM RoleIAM PolicyScope
System Admin SystemAdminRole SystemAdminPolicy All tenants
SystemSuperuserRole (inherits admin) All tenants
SystemSupportRole SystemSupportPolicy All tenants
Customer Tenant TenantAdminRole TenantAdminPolicy Own tenant only
TenantUserRole TenantUserPolicy Own tenant only

🔑 Authentication

Cognito-based authentication with per-tenant user pools. A single user credential works across Dev, Staging and Prod environments.

User Initiates Sign-In
Client app (HR Admin or ESS Mobile via AWS Amplify) sends credentials to the tenant's Cognito User Pool.
Pre-Authentication Lambda Trigger
Determines the user's target environment (Dev, Staging, or Prod) and validates access eligibility.
Cognito Authenticates User
Standard Cognito authentication flow (SRP or USER_PASSWORD_AUTH).
Pre-Token-Generation Lambda Trigger
Injects custom claims into the JWT: tenantId, tenantRole, environment, and other tenant-specific metadata.
JWT Issued to Client
Client receives ID token, Access token, and Refresh token. Bearer JWT is used for all subsequent API requests.
API Request with Bearer Token
Client attaches the JWT as a Bearer token in the Authorization header for every API call.

JWT Token Structure

Claim TypeClaimDescription
StandardsubCognito user UUID
StandardemailUser's email
StandardissCognito user pool issuer URL
StandardexpToken expiration
Customcustom:tenantIdTenant identifier
Customcustom:tenantRoleRole within the tenant (admin, user, etc.)
Customcustom:environmentTarget environment (Dev, Staging, Prod)
📱
ESS Mobile: Integrated with AWS Amplify library for all user operations including sign-in, sign-out, token refresh, and MFA.

Authorization — Amazon Verified Permissions

Fine-grained authorization using AVP. Each tenant has a dedicated policy store with schema, policy templates, and policies integrated with their Cognito User Pool and the API Gateway.

🔑
JWT Token
Custom claims
🛡
Lambda Authorizer
Extracts principal
📜
AVP Policy Store
Tenant-specific
📋
Authorization Decision
ALLOW → Forward to service
DENY → 403 Forbidden
← Scroll horizontally if needed →

Per-Tenant Policy Store Components

ComponentDescription
SchemaDefines entity types, actions, and resource types. Integrated with the tenant's Cognito User Pool and API Gateway API.
Policy TemplatesReusable authorization rule templates parameterized by principal and resource.
PoliciesConcrete policies instantiated from templates, granting or denying specific actions to specific principals on specific resources.

Backend Microservices

OpenAPI-based Express.js applications deployed as AWS Lambda functions. Each service owns its domain and exposes RESTful endpoints via API Gateway.

💼
Employment
Employee lifecycle: hiring, contracts, transfers, terminations, job assignments.
Lambda + Express.js
👤
PIM
Personal Information Management: employee profiles, contact info, documents.
Lambda + Express.js
📱
MeAtWork (ESS)
Main Employee Self-Service API. Personal data, requests, approvals from mobile.
Lambda + Express.js
📅
Work Schedule
Shift patterns, rosters, schedule assignments, clock-in/out configuration.
Lambda + Express.js
🌴
Leave
Leave types, balances, requests, approvals, accrual policies.
Lambda + Express.js
Time & Attendance
Clock events, attendance tracking, timesheet generation, anomaly detection.
Lambda + Express.js
Overtime
OT requests, approvals, calculations, policy enforcement.
Lambda + Express.js
💰
Payroll
Salary processing, deductions, tax calculations, payslip generation.
Lambda + Express.js
🔔
Notifications
Push notifications, email alerts, in-app messages, notification preferences.
Lambda + Express.js
🔒
IAM
Identity & Access Management. Tenant user provisioning, roles, permissions.
Lambda + Express.js
📦
SRM
Service/resource management operations.
Lambda + Express.js
📜
All microservices are documented with OpenAPI specifications and exposed via a single Amazon API Gateway REST API. Each service is a separate Lambda function running Express.js behind the API Gateway.

🗃 Storage

Dual storage strategy: DynamoDB for structured tenant data with IAM-enforced isolation, S3 for object/document storage.

🗃 Amazon DynamoDB

Primary data store. Shared tables with tenant-scoped access.

  • Pool model — all tenants share tables
  • Composite key: TenantId (PK) + EntityItem (SK)
  • IAM policy-level data isolation
  • Environment-suffixed tables (Dev/Prod)

📦 Amazon S3

Object storage for documents and files.

  • Employee documents & attachments
  • Payslips & reports (generated)
  • Profile photos
  • Tenant-prefixed key structure

Key DynamoDB Tables

TableEnvironment VariantsUsed By
EmploymentEmployment[Dev|Prod]Employment service, JobPicker
TenantJobTenantJob[Dev|Prod]System Jobs (general scheduled jobs)
WorkScheduleWorkSchedule[Dev|Prod]Work Schedule service, JobPicker
PIMPIM[Dev|Prod]PIM service
LeaveLeave[Dev|Prod]Leave service
TimeAttendanceTimeAttendance[Dev|Prod]T&A service
PayrollPayroll[Dev|Prod]Payroll service

System Jobs

Scheduled background jobs for automated processing. EventBridge triggers a JobPicker Lambda every minute, which scans DynamoDB for pending jobs and dispatches them to specialized runner functions.

Job Execution Pipeline

📅
EventBridge Schedule
DEV AIML-SaaS-Scheduler-Dev
STG AIML-SaaS-Scheduler-Staging
PROD AIML-SaaS-Scheduler-Prod
Fires every 1 minute
🔨
JobPicker Lambda
Scans tables for due jobs
🗃 Source Tables
TenantJob[Dev|Prod]
WorkSchedule[Dev|Prod]
Employment[Dev|Prod]
🔍
JobPicker queries each table for items where scheduled time ≤ now
Dispatches to
⚙ AIML-SaaS-Job-Runner
⚙ AIML-SaaS-AttGen-System
⚙ AIML-SaaS-AttGen-Tenant
⚙ AIML-SaaS-Employee-Earn-AL
⚙ AIML-SaaS-CRUD-Item
⚙ AIML-SaaS-Tenant-Notification-Dispatcher
⚙ (other runners...)

Job Scope

Runner LambdaScopeExample Operations
AIML-SaaS-Job-RunnerGeneralContract start/end processing
AIML-SaaS-AttGen-SystemSystem-wideGenerate attendance records across all tenants
AIML-SaaS-AttGen-TenantPer-tenantGenerate attendance records for a specific tenant
AIML-SaaS-Employee-Earn-ALPer-tenantAnnual leave accrual calculations
AIML-SaaS-CRUD-ItemPer-tenantUpdate a single entity item at tenant level
AIML-SaaS-Tenant-Notification-DispatcherPer-tenantSend scheduled notifications (clock-in/out alerts)

📊 Reports & Analytics (BI)

Business intelligence and reporting capabilities.

🚧
Not yet defined. This section is a placeholder for the reporting and analytics architecture. See the Suggestions section for recommended approaches.

💼 Tenant Accounts & Subscription Management

Centralized tenant lifecycle and subscription management. Currently in development.

🖥
Django Admin App
● Python / Django framework
● Tenant onboarding & provisioning
● Subscription management
● Billing & usage tracking
Python + Django
🛡
API Gateway + Lambda
Exposes management APIs
🗃
PostgreSQL Serverless
● Tenant accounts
● Subscription plans
● Billing records
● Feature flags
🚧
Status: In progress. Architecture and implementation are actively being developed.

💡 Architectural Suggestions

Recommended additions and improvements for the OptimalHR platform based on common SaaS patterns.

📊 BI & Reporting Pipeline
Export DynamoDB data via DynamoDB Streams → Kinesis Data Firehose → S3 (Parquet) → Athena for SQL analytics. Layer QuickSight dashboards for tenant-facing and system-level reporting.
HIGH PRIORITY
📋 API Versioning Strategy
Implement API versioning (e.g., /v1/, /v2/) at the API Gateway level. Enables non-breaking evolution of microservice APIs across tenant upgrade cycles.
HIGH PRIORITY
🚨 Observability & Monitoring
Centralized logging with CloudWatch Logs Insights, X-Ray distributed tracing across Lambda services, and CloudWatch alarms for error rates, latency (P99), and DynamoDB throttling.
HIGH PRIORITY
🚀 CI/CD Pipeline
Automated deployment pipeline using CodePipeline or GitHub Actions. Separate stages for Dev → Staging → Prod with approval gates and automated integration tests.
HIGH PRIORITY
🔨 Dead Letter Queues (DLQ)
Add SQS DLQs to all Lambda functions (especially job runners) to capture failed invocations. Monitor DLQ depth and set up alarms for failed job processing.
MEDIUM
🔐 Secrets Management
Use AWS Secrets Manager or SSM Parameter Store for database credentials, API keys, and third-party integration secrets. Rotate secrets automatically.
MEDIUM
🚀 Tenant Provisioning Automation
Automate tenant onboarding: create Cognito user pool, identity pool, IAM roles, AVP policy store, and seed data via a Step Functions workflow triggered from the Django admin.
MEDIUM
🌐 CDN & Edge Caching
Add CloudFront distributions for API responses (with tenant-aware cache keys) and S3 objects. Reduces latency for geographically distributed tenant users.
NICE TO HAVE
🛠 Feature Flags
Implement per-tenant feature flags (via AppConfig or LaunchDarkly) to control feature rollouts, A/B testing, and subscription-tier gating without redeployment.
NICE TO HAVE
📈 Rate Limiting & Throttling
Configure per-tenant API usage plans and throttling at API Gateway. Prevents noisy-neighbor problems and aligns with subscription tiers.
MEDIUM

🌐 Environment Matrix

Resource naming conventions and environment breakdown.

ResourceDevStagingProd
EventBridge Schedule AIML-SaaS-Scheduler-Dev AIML-SaaS-Scheduler-Staging AIML-SaaS-Scheduler-Prod
DynamoDB Tables *Dev suffix *Staging suffix *Prod suffix
User Access Single user credential works across all environments (env determined at pre-auth)