Anik Sikder
Blueprints/talentflow-hcm-human-capital-management-platform

TalentFlow HCM (Human Capital Management Platform)

Enterprise-grade human capital management platform designed to unify employee lifecycle management, payroll operations, recruitment workflows, attendance tracking, performance management, and workforce analytics within a secure multi-tenant architecture.

HCMHRMSPayrollRecruitmentWorkforce ManagementSaaSMulti-TenantHR TechnologyDjangoCQRSEvent Driven Architecture
32 min readMay 1, 2026
  • Read Time
    32 min read
  • Topics
    11
  • Patterns
    7
  • Level
    Advanced
Architecture Highlights

WebSocket-first real-time communication

Versioned contracts and acceptance workflows

Double-entry escrow and wallet ledger

Atomic milestone state transitions

Transactional outbox and idempotent events

Dispute-driven fund freezing

CQRS read models for messaging and transactions

blueprint.md

$ open blueprint

TalentFlow HCM is designed as a workforce operating system rather than a collection of disconnected HR CRUD modules.

The platform manages the complete employee lifecycle:

code
Candidate
   │
   ▼
Recruitment
   │
   ▼
Offer
   │
   ▼
Onboarding
   │
   ▼
Active Employee
   │
   ├──────────────► Attendance
   │
   ├──────────────► Leave
   │
   ├──────────────► Compensation
   │
   ├──────────────► Payroll
   │
   ├──────────────► Performance
   │
   └──────────────► Workforce Analytics
   │
   ▼
Transfer / Promotion / Compensation Change
   │
   ▼
Offboarding
   │
   ▼
Employment History

The important architectural decision is that an employee is not treated as a static database record.

An employee is the current representation of a continuously changing employment relationship.

That means:

code
Employee
+
Employment History
+
Organizational Assignment
+
Compensation History
+
Attendance
+
Leave
+
Performance
+
Payroll
+
Lifecycle Events

must be modeled as related but independently governed domains.


Business Problem

A traditional HR system often starts as a simple employee table:

code
Employee
---------
name
department
salary
joining_date
status

This works until the organization starts changing.

An employee can:

  • change departments
  • change managers
  • receive a promotion
  • receive a salary adjustment
  • move between branches
  • change employment type
  • take leave
  • work different shifts
  • receive bonuses
  • become eligible for new benefits
  • participate in performance cycles
  • leave the organization

If the system simply overwrites the employee record, historical truth disappears.

For example:

code
Employee.salary = 150000

does not answer:

code
What was the salary in January?

Who approved the increase?

When did it become effective?

Which payroll period used the old salary?

Which payroll period used the new salary?

What compensation structure was active at that time?

Therefore TalentFlow treats workforce state as time-aware domain data rather than a collection of mutable fields.


Architectural Goals

The platform is designed around several non-negotiable requirements.

RequirementArchitectural Decision
Tenant isolationOrganization-scoped data access
Employee historyEffective-dated records
Payroll correctnessTransactional calculation + immutable payroll results
Workflow governanceExplicit state machines
AuthorizationRBAC + resource-level scope
ReportingCQRS read models
Background processingCelery workers
Real-time stateRedis
AuditabilityAppend-only audit events
IntegrationAPI + domain events
ReliabilityIdempotent commands and jobs
ScalabilityDomain separation + asynchronous processing

The system is intentionally designed so that operational correctness does not depend on dashboards, background jobs, or external integrations.


High-Level Architecture Blueprint

code
                         ┌───────────────────────────────┐
                         │       Client Applications      │
                         │                               │
                         │  Web │ Mobile │ Admin │ ESS   │
                         └───────────────┬───────────────┘
                                         │
                                         ▼
                         ┌───────────────────────────────┐
                         │          API Gateway           │
                         │                               │
                         │ JWT │ Tenant Context │ RBAC    │
                         │ Rate Limit │ Validation        │
                         └───────────────┬───────────────┘
                                         │
              ┌──────────────────────────┼──────────────────────────┐
              │                          │                          │
              ▼                          ▼                          ▼
     ┌─────────────────┐       ┌─────────────────┐       ┌─────────────────┐
     │ Employee Domain │       │ Recruitment     │       │ Payroll Domain  │
     │                 │       │ Domain          │       │                 │
     │ Lifecycle       │       │ Candidates      │       │ Payroll Runs    │
     │ Employment      │       │ Jobs            │       │ Salary          │
     │ Org Assignment  │       │ Interviews      │       │ Benefits        │
     └────────┬────────┘       └────────┬────────┘       └────────┬────────┘
              │                         │                         │
              │                         │                         │
              ▼                         ▼                         ▼
     ┌─────────────────┐       ┌─────────────────┐       ┌─────────────────┐
     │ Attendance      │       │ Leave Domain    │       │ Performance     │
     │ Domain          │       │                 │       │ Domain          │
     │                 │       │ Requests        │       │ Goals           │
     │ Timesheets      │       │ Policies        │       │ Reviews         │
     │ Shifts          │       │ Balances        │       │ Cycles          │
     └────────┬────────┘       └────────┬────────┘       └────────┬────────┘
              │                         │                         │
              └─────────────────────────┼─────────────────────────┘
                                        │
                                        ▼
                           ┌─────────────────────────┐
                           │     Domain Event Layer  │
                           │                         │
                           │ Employee Events         │
                           │ Payroll Events          │
                           │ Attendance Events       │
                           │ Leave Events            │
                           │ Performance Events      │
                           └────────────┬────────────┘
                                        │
                         ┌──────────────┼──────────────┐
                         │              │              │
                         ▼              ▼              ▼
                 ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
                 │ PostgreSQL  │ │ Redis       │ │ Celery      │
                 │             │ │             │ │ Workers     │
                 │ Source of   │ │ Cache /     │ │             │
                 │ Truth       │ │ Locks       │ │ Async Jobs  │
                 └──────┬──────┘ └─────────────┘ └──────┬──────┘
                        │                               │
                        ▼                               ▼
                ┌──────────────┐               ┌────────────────────┐
                │ Read Models  │               │ External Systems   │
                │              │               │                    │
                │ Analytics    │               │ Email              │
                │ Dashboards   │               │ Banking            │
                │ Reports      │               │ Tax APIs           │
                └──────────────┘               │ Attendance Devices │
                                               └────────────────────┘

Domain Architecture

The platform is divided into bounded domains rather than one large EmployeeService.

code
TalentFlow
│
├── Identity & Access
│
├── Organization
│
├── Employee
│
├── Recruitment
│
├── Onboarding
│
├── Compensation
│
├── Attendance
│
├── Leave
│
├── Payroll
│
├── Performance
│
├── Workflow
│
├── Notification
│
├── Audit
│
└── Analytics

Each domain owns its business rules and persistence boundaries.

A domain can publish events to other domains, but should not directly modify another domain's tables.

Example

Bad:

code
employee.salary = 180000
employee.save()

payroll.salary = employee.salary
payroll.save()

This creates hidden coupling.

Better:

code
Compensation Domain
        │
        ▼
CompensationChanged
        │
        ├────────► Payroll
        │
        ├────────► Analytics
        │
        └────────► Audit

The compensation domain owns the change.

Other domains react to the fact that the change occurred.


Tenant Architecture

TalentFlow is designed as a multi-tenant SaaS platform.

code
Platform
   │
   ├── Organization A
   │      ├── Branches
   │      ├── Departments
   │      ├── Employees
   │      └── Payroll
   │
   ├── Organization B
   │      ├── Branches
   │      ├── Departments
   │      ├── Employees
   │      └── Payroll
   │
   └── Organization C
          ├── Branches
          ├── Departments
          ├── Employees
          └── Payroll

Every tenant-owned record carries organization context.

code
class TenantModel(models.Model):
    organization = models.ForeignKey(
        Organization,
        on_delete=models.PROTECT,
    )

    class Meta:
        abstract = True

Tenant context should be established before business logic executes.

code
JWT
 │
 ▼
User
 │
 ▼
Organization Membership
 │
 ▼
Tenant Context
 │
 ▼
Permission Evaluation
 │
 ▼
Domain Operation

A request should never be able to choose its organization arbitrarily.

Bad:

code
GET /api/employees?organization_id=abc

Good:

code
Authenticated User
       │
       ▼
Resolved Organization Context
       │
       ▼
Query automatically scoped to tenant

Tenant Isolation

Every query must be tenant-safe.

Bad:

code
Employee.objects.filter(id=employee_id)

Better:

code
Employee.objects.filter(
    organization=request.organization,
    id=employee_id,
)

Even better is to make tenant scoping part of the repository/query layer so developers cannot accidentally omit it.

code
EmployeeQuery.for_tenant(
    organization=request.organization
).get(id=employee_id)

The tenant boundary is therefore treated as a security invariant rather than a developer convention.


Organizational Hierarchy

Organizations usually have more than a flat employee list.

code
Organization
   │
   ├── Business Unit
   │      │
   │      ├── Department
   │      │      │
   │      │      ├── Team
   │      │      │
   │      │      └── Employees
   │      │
   │      └── Department
   │
   └── Branch
          │
          └── Employees

The organizational model must support:

  • departments
  • teams
  • branches
  • managers
  • reporting relationships
  • cost centers
  • job positions
  • employment types

An employee's organizational assignment should therefore be effective dated.

code
Employee
   │
   └── EmploymentAssignment
          ├── department
          ├── manager
          ├── branch
          ├── position
          ├── effective_from
          └── effective_to

This prevents historical organizational data from being destroyed.


Employee Lifecycle Architecture

Employee lifecycle is modeled as an explicit state machine.

code
Candidate
   │
   ▼
Offer Extended
   │
   ▼
Offer Accepted
   │
   ▼
Onboarding
   │
   ▼
Active
   │
   ├──────────────► Leave of Absence
   │
   ├──────────────► Suspended
   │
   ├──────────────► Transferred
   │
   └──────────────► Promoted
   │
   ▼
Notice Period
   │
   ▼
Offboarding
   │
   ▼
Terminated

The state transition itself is a business operation.

Bad:

code
employee.status = "terminated"
employee.save()

Good:

code
EmployeeLifecycleService.terminate(
    employee=employee,
    effective_date=date,
    reason=reason,
    actor=user,
)

The service validates:

  • current lifecycle state
  • notice requirements
  • pending payroll
  • outstanding leave
  • company assets
  • approval requirements
  • termination policy

and then creates the required domain events.


Employee Identity vs Employment Relationship

One of the most important modeling decisions is separating the person from their employment relationship.

code
Person
 │
 ├── Personal Information
 │
 └── Identity
       │
       ▼
Employee
       │
       ├── Employment
       ├── Compensation
       ├── Assignments
       ├── Attendance
       └── Performance

A person may have multiple employment relationships over time.

For example:

code
Person
 │
 ├── Employment #1
 │      └── Employee at Company A
 │
 └── Employment #2
        └── Employee at Company B

This distinction becomes particularly important in multi-tenant systems, rehiring scenarios, contractors, and historical reporting.


Compensation Architecture

Compensation should not be stored simply as:

code
employee.salary = 150000

Instead:

code
Employee
   │
   ▼
Compensation Profile
   │
   ├── Base Salary
   ├── Allowances
   ├── Bonuses
   ├── Benefits
   ├── Deductions
   └── Effective Dates

Example:

code
CompensationVersion
────────────────────────────
employee_id
effective_from
effective_to
currency
base_salary
pay_frequency
status
approved_by

This allows payroll to determine exactly which compensation version applied to a specific payroll period.


Compensation Change Flow

code
Manager Requests Salary Change
        │
        ▼
Approval Workflow
        │
        ▼
HR Review
        │
        ▼
Finance Approval
        │
        ▼
Compensation Version Created
        │
        ▼
CompensationChanged Event
        │
        ├────────► Payroll
        ├────────► Employee Profile
        ├────────► Analytics
        └────────► Audit

The new salary should not overwrite the old salary.

Instead:

code
Old Compensation
effective: Jan 1 → Jun 30

New Compensation
effective: Jul 1 → future

Historical payroll can therefore always reconstruct the correct inputs.


Recruitment Architecture

Recruitment is treated as a separate bounded domain.

code
Job Requisition
      │
      ▼
Approval
      │
      ▼
Job Published
      │
      ▼
Candidate Application
      │
      ▼
Screening
      │
      ▼
Interview
      │
      ▼
Evaluation
      │
      ▼
Offer
      │
      ▼
Offer Accepted
      │
      ▼
Onboarding

Candidate state is explicit.

code
Applied
   │
   ▼
Screening
   │
   ├──► Rejected
   │
   ▼
Interview
   │
   ├──► Rejected
   │
   ▼
Offer
   │
   ├──► Declined
   │
   ▼
Hired

Recruitment should publish:

code
CandidateHired

rather than directly creating payroll or employee records across modules.


Recruitment to Employee Transition

code
Candidate
   │
   ▼
Offer Accepted
   │
   ▼
Onboarding Workflow
   │
   ├── Create Employee Identity
   ├── Assign Department
   ├── Assign Position
   ├── Create Compensation Profile
   ├── Configure Leave Eligibility
   └── Configure Payroll Profile
   │
   ▼
Employee Activated

This creates a controlled transition from recruitment state to workforce state.


Onboarding Workflow Engine

Onboarding is modeled as a workflow rather than a checklist field.

code
Onboarding
    │
    ├── Identity Verification
    ├── Document Collection
    ├── Contract Signing
    ├── Payroll Setup
    ├── Benefits Enrollment
    ├── Equipment Assignment
    └── Manager Confirmation

Each task has:

code
Task
────────────────
workflow_id
type
assignee
status
due_date
completed_at
completed_by

Workflow state:

code
Pending
   │
   ▼
In Progress
   │
   ▼
Completed

Tasks may run sequentially or in parallel.

code
                 ┌── Payroll Setup
                 │
Onboarding ──────┼── Document Verification
                 │
                 ├── Equipment Assignment
                 │
                 └── Manager Confirmation

The onboarding workflow completes only when required tasks are complete.


Attendance Architecture

Attendance is modeled as raw events plus derived attendance state.

code
Clock In
   │
   ▼
Attendance Event
   │
   ▼
Attendance Processing
   │
   ▼
Daily Attendance Record
   │
   ├── Worked Hours
   ├── Late Minutes
   ├── Overtime
   └── Absence

Raw attendance events should not be overwritten.

code
AttendanceEvent
────────────────────
employee
event_type
event_time
source
device_id
external_event_id

Sources may include:

code
Web
Mobile
Biometric Device
Import
Admin Adjustment

Attendance Idempotency

Biometric devices and external systems frequently retry events.

Therefore every external attendance event should carry an idempotency key.

code
device_id + external_event_id

Database constraint:

code
UNIQUE(device_id, external_event_id)

Then:

code
AttendanceEvent.objects.get_or_create(
    device_id=device_id,
    external_event_id=external_event_id,
    defaults=payload,
)

A repeated device request cannot create duplicate attendance.


Attendance Processing

Attendance calculation should be separated from event ingestion.

code
Raw Attendance Event
        │
        ▼
Validation
        │
        ▼
Normalization
        │
        ▼
Attendance Processor
        │
        ▼
Daily Attendance Projection
        │
        ├── Worked Hours
        ├── Overtime
        ├── Late
        └── Absence

This allows raw events to remain immutable while attendance calculations can be corrected or recalculated later.


Leave Management Architecture

Leave is governed by policy, balance, approval, and calendar constraints.

code
Employee
   │
   ▼
Leave Request
   │
   ▼
Policy Validation
   │
   ├── Balance Available?
   ├── Date Valid?
   ├── Overlap?
   ├── Holiday?
   └── Approval Required?
   │
   ▼
Approval Workflow
   │
   ▼
Approved
   │
   ▼
Leave Balance Updated

A leave balance should not simply be manually edited.

Instead:

code
Opening Balance
+
Accruals
-
Approved Leave
+
Adjustments
=
Available Balance

This provides traceability.


Leave Ledger

code
LeaveLedgerEntry
────────────────────────
employee
leave_type
quantity
entry_type
reference
created_at

Examples:

code
Opening Balance      +20
Monthly Accrual       +2
Approved Leave        -3
Administrative Adj.   +1

Current balance becomes a projection of these entries.

This prevents unexplained balance mutations.


Payroll Architecture

Payroll is the most consistency-sensitive domain in the platform.

Payroll should not be:

code
salary * days_worked

It is a controlled calculation pipeline.

code
Payroll Period
      │
      ▼
Eligibility Snapshot
      │
      ▼
Compensation Snapshot
      │
      ▼
Attendance Snapshot
      │
      ▼
Leave Snapshot
      │
      ▼
Benefits
      │
      ▼
Deductions
      │
      ▼
Tax / Statutory Rules
      │
      ▼
Gross Pay
      │
      ▼
Net Pay
      │
      ▼
Payroll Review
      │
      ▼
Payroll Approval
      │
      ▼
Payroll Finalization
      │
      ▼
Payment Instruction

The key architectural principle:

Finalized payroll must be immutable.


Payroll Run State Machine

code
Draft
  │
  ▼
Calculating
  │
  ▼
Calculated
  │
  ▼
Under Review
  │
  ├──► Recalculation
  │
  ▼
Approved
  │
  ▼
Finalized
  │
  ▼
Payment Processing
  │
  ▼
Paid

Failure state:

code
Calculating
     │
     ▼
Calculation Failed
     │
     ▼
Retry / Recalculate

A finalized payroll run should never be modified in place.

Corrections should create adjustment records or correction runs.


Payroll Snapshotting

Payroll cannot depend on today's employee state.

Suppose:

code
Employee salary changed on July 15.
Payroll period: July 1 → July 31.

The payroll engine needs to know exactly which compensation rules applied to the period.

Therefore payroll creates a snapshot.

code
Payroll Run
   │
   ├── Employee Snapshot
   ├── Compensation Snapshot
   ├── Attendance Snapshot
   ├── Leave Snapshot
   ├── Benefit Snapshot
   └── Deduction Snapshot

This makes payroll reproducible.

If an employee's profile changes tomorrow, finalized payroll remains unchanged.


Payroll Calculation Engine

Payroll calculation should be composed of deterministic steps.

code
Base Salary
     │
     ▼
Proration
     │
     ▼
Allowances
     │
     ▼
Overtime
     │
     ▼
Bonus
     │
     ▼
Gross Earnings
     │
     ▼
Pre-Tax Deductions
     │
     ▼
Tax / Statutory Deductions
     │
     ▼
Post-Tax Deductions
     │
     ▼
Net Pay

Each calculation should produce explainable line items.

code
PayrollLineItem
────────────────────────
type
description
quantity
rate
amount
calculation_reference

Instead of:

code
Net salary = 143,500

the system can explain:

code
Base Salary             120,000
Housing Allowance        15,000
Transport                 5,000
Overtime                  8,500
Gross                    148,500

Tax                       3,000
Other Deduction            2,000

Net                      143,500

This is critical for payroll auditability.


Payroll Concurrency Control

Only one payroll run should be actively finalized for a given tenant and period.

A database constraint can enforce this invariant.

code
organization_id
pay_period_id
status

with business rules preventing multiple active/finalized runs.

Finalization should use a transaction.

code
with transaction.atomic():

    payroll_run = (
        PayrollRun.objects
        .select_for_update()
        .get(id=run_id)
    )

    if payroll_run.status != "approved":
        raise InvalidPayrollState()

    payroll_run.status = "finalized"
    payroll_run.finalized_at = timezone.now()
    payroll_run.save()

The important point is that the state transition and its invariants are protected by the database transaction.


Payroll Payment Architecture

Payroll calculation and payroll payment should be separate concerns.

code
Payroll Engine
      │
      ▼
Finalized Payroll
      │
      ▼
Payment Instruction
      │
      ▼
Bank / Payment Provider
      │
      ▼
External Confirmation
      │
      ▼
Payment Reconciliation

External payment APIs are unreliable.

They may:

  • timeout
  • retry
  • return duplicate callbacks
  • process successfully but fail to respond
  • deliver webhooks out of order

Therefore payment instructions need idempotency keys.

code
organization_id
payroll_run_id
employee_id
payment_reference

Payment Reconciliation

Never assume:

code
API request succeeded
=
Money was transferred

Instead:

code
Payment Requested
       │
       ▼
Submitted
       │
       ▼
Provider Processing
       │
       ├──► Failed
       │
       ▼
Completed
       │
       ▼
Reconciled

Webhook events should be persisted and processed idempotently.


Performance Management

Performance management is modeled as a recurring cycle.

code
Performance Cycle
        │
        ▼
Goal Setting
        │
        ▼
Progress Updates
        │
        ▼
Manager Review
        │
        ▼
Peer / 360 Feedback
        │
        ▼
Final Evaluation
        │
        ▼
Calibration
        │
        ▼
Cycle Closed

A performance review should reference the specific cycle and employee assignment that existed during that period.


Goal Architecture

Goals are structured objects.

code
Goal
────────────────
employee
cycle
title
description
metric
target
weight
status

Performance score:

code
Goal Score × Goal Weight
        │
        ▼
Weighted Performance Score

This allows analytics to distinguish:

code
Goal achievement
vs
Manager rating
vs
Peer feedback

instead of collapsing everything into one opaque score.


Approval Workflow Engine

Several domains require approvals:

code
Salary Change
Leave Request
Job Requisition
Expense
Promotion
Termination
Payroll

Building approval logic separately in every module creates duplication.

TalentFlow therefore uses a reusable workflow abstraction.

code
Business Object
      │
      ▼
Workflow Definition
      │
      ▼
Approval Steps
      │
      ├── Manager
      ├── HR
      ├── Finance
      └── Executive
      │
      ▼
Workflow Decision

Example:

code
Salary Increase
      │
      ▼
Manager Approval
      │
      ▼
HR Approval
      │
      ▼
Finance Approval
      │
      ▼
Effective Compensation Change

Workflow State Machine

code
Pending
  │
  ▼
In Progress
  │
  ├──► Rejected
  │
  ├──► Returned
  │
  ▼
Approved
  │
  ▼
Executed

A workflow transition should be explicit.

Bad:

code
request.status = "approved"

Good:

code
workflow.approve(
    actor=user,
    comment=comment,
)

The workflow engine validates:

  • actor permission
  • current state
  • required approval level
  • delegation
  • organization scope
  • transition validity

RBAC Architecture

Authorization operates at multiple levels.

code
Platform Role
      │
      ▼
Organization Role
      │
      ▼
Resource Scope
      │
      ▼
Action Permission

Example:

code
HR Manager
   │
   ├── View Employees
   ├── Edit Employee Profile
   ├── Approve Leave
   └── View Payroll

But:

code
Branch HR Manager

may only access:

code
Employees
WHERE branch_id = assigned_branch

Therefore role checking alone is insufficient.

The authorization decision becomes:

code
Can User
   perform Action
   on Resource
   within Tenant
   within Scope?

Data Access Policy

A permission should be evaluated close to the data boundary.

code
EmployeePolicy.can_view(
    user=user,
    employee=employee,
)

not merely:

code
if user.is_hr:
    allow()

because authorization depends on:

  • tenant
  • role
  • branch
  • department
  • reporting relationship
  • resource ownership
  • action

Event-Driven Architecture

Domains communicate through events.

Example:

code
EmployeeActivated
        │
        ├────────► Payroll
        ├────────► Leave
        ├────────► Attendance
        ├────────► Notification
        └────────► Analytics

Another:

code
CompensationChanged
        │
        ├────────► Payroll
        ├────────► Analytics
        └────────► Audit

Another:

code
LeaveApproved
        │
        ├────────► Attendance
        ├────────► Payroll
        ├────────► Calendar
        └────────► Notification

The producer does not need to know how consumers implement their logic.


Transactional Event Publishing

A major failure mode in event-driven systems is:

code
Database transaction succeeds
        │
        ▼
Application crashes
        │
        ▼
Event never published

To avoid this, TalentFlow should use an outbox pattern.

code
Business Transaction
       │
       ├────────► Domain State
       │
       └────────► Outbox Event
                    │
                    ▼
              Commit Transaction
                    │
                    ▼
             Outbox Publisher
                    │
                    ▼
               Event Broker

Example:

code
with transaction.atomic():

    employee.activate()

    OutboxEvent.objects.create(
        event_type="employee.activated",
        aggregate_id=employee.id,
        payload=payload,
    )

The event and business state commit together.

A worker then publishes the event asynchronously.


Outbox Processing

code
Outbox Table
     │
     ▼
Unpublished Event
     │
     ▼
Publisher Worker
     │
     ▼
Message Broker
     │
     ▼
Mark Published

If publishing fails:

code
Retry
   │
   ▼
Retry
   │
   ▼
Retry

No business event is silently lost.


Idempotent Event Consumers

At-least-once event delivery means consumers may receive the same event multiple times.

Therefore:

code
EmployeeActivated
EmployeeActivated
EmployeeActivated

must produce the same final result as one event.

Consumer pattern:

code
Event ID
   │
   ▼
ProcessedEvent
   │
   ├── Exists → Ignore
   │
   └── Missing
         │
         ▼
      Process
         │
         ▼
      Record Event ID

This makes retries safe.


CQRS Architecture

Operational writes and analytical reads have different requirements.

code
Command Side
─────────────
Create Employee
Approve Leave
Finalize Payroll
Record Attendance
Approve Promotion

        │
        ▼

Transactional PostgreSQL


Read Side
──────────
Headcount Dashboard
Payroll Reports
Turnover Analytics
Attendance Dashboard
Recruitment Funnel
Performance Analytics

Read models are optimized specifically for queries.


Workforce Analytics Read Model

Instead of repeatedly calculating:

code
COUNT(employees)
JOIN departments
JOIN branches
JOIN employment_history
JOIN attendance
...

the platform maintains projections.

code
WorkforceSnapshot
──────────────────────
organization
date
headcount
active_employees
new_hires
terminations
on_leave
average_tenure
payroll_total

Dashboard queries then become simple and fast.


Analytics Projection Flow

code
EmployeeActivated
EmployeeTerminated
EmployeeTransferred
LeaveApproved
PayrollFinalized
        │
        ▼
Analytics Consumers
        │
        ▼
Read Model
        │
        ▼
Dashboard

If the projection is corrupted:

code
Event History
      │
      ▼
Replay
      │
      ▼
Rebuild Read Model

This is one of the major benefits of event-driven architecture.


Redis Architecture

Redis should not become the source of truth.

It is used for:

code
Redis
│
├── API caching
├── Session / temporary state
├── Distributed locks
├── Rate limiting
├── Job coordination
└── Frequently accessed read projections

Examples:

code
employee:{id}
organization:{id}:settings
payroll:{run_id}:status

Cached data must always be disposable.

If Redis disappears:

code
System remains correct.

It may become slower temporarily, but correctness does not depend on cache state.


Distributed Locking

Certain operations require protection from concurrent execution.

Examples:

code
Payroll Finalization
Payroll Calculation
Leave Balance Rebuild
Employee Lifecycle Transition
Bulk Attendance Processing

Redis can provide a distributed lock, but the database remains the final consistency boundary.

code
Request
  │
  ▼
Acquire Lock
  │
  ▼
Database Transaction
  │
  ▼
Commit
  │
  ▼
Release Lock

Locks improve coordination.

They should not replace database constraints.


Background Processing

Celery handles workloads that should not block API requests.

code
API Request
    │
    ├── Immediate validation
    │
    ├── Transaction
    │
    └── Queue Async Work
              │
              ▼
           Celery
              │
       ┌──────┼───────┐
       ▼      ▼       ▼
    Email   Payroll  Analytics

Typical asynchronous jobs:

  • payroll calculation
  • bulk attendance processing
  • employee notifications
  • report generation
  • analytics projection
  • onboarding reminders
  • leave accrual
  • payroll payment reconciliation

Job Reliability

Every important asynchronous job should be:

code
Idempotent
Retryable
Observable
Bounded

For example:

code
Generate Payroll
       │
       ▼
Job ID
       │
       ▼
Check Existing Run
       │
       ├── Already completed → return
       │
       ▼
Process
       │
       ▼
Persist result

A retry must not create a second payroll run.


Audit Architecture

HR systems require strong auditability.

Important actions should produce immutable audit records.

code
AuditEvent
────────────────────────
organization
actor
action
resource_type
resource_id
before
after
timestamp
ip_address
request_id

Examples:

code
Salary Changed
Employee Terminated
Payroll Approved
Leave Approved
Role Changed
Employee Record Updated

Audit records should be append-only.

Bad:

code
audit.status = "corrected"

Better:

code
Original Audit Event
        │
        ▼
Correction Event

The historical record remains intact.


Audit vs Application Logs

These should not be confused.

Application logs:

code
API request failed
Database timeout
Worker crashed

Audit events:

code
HR Manager changed employee salary
Finance approved payroll
Admin changed employee role

Application logs help engineers debug the system.

Audit events help organizations prove what happened.


Employee Self-Service Architecture

Employee self-service should operate through the same domain APIs as administrative interfaces.

code
Employee Portal
      │
      ▼
API
      │
      ▼
Authorization
      │
      ▼
Employee Scope

Employees can:

code
View Profile
Request Leave
View Payslips
Update Allowed Information
Submit Attendance Correction
View Goals
Complete Reviews

But they cannot directly modify governed records.

For example:

code
Employee changes bank account
        │
        ▼
Verification / Approval Workflow
        │
        ▼
Account Updated

not:

code
employee.bank_account = request.data["bank_account"]

API Architecture

The API layer follows:

code
HTTP
 │
 ▼
Serializer / Request Validation
 │
 ▼
Application Command
 │
 ▼
Domain Service
 │
 ▼
Repository
 │
 ▼
PostgreSQL

The API should not contain domain logic.

Bad:

code
class ApproveLeaveView(APIView):

    def post(self, request):

        if request.user.is_manager:
            leave.status = "approved"

        leave.save()

Better:

code
class ApproveLeaveView(APIView):

    def post(self, request, leave_id):

        command = ApproveLeaveCommand(
            leave_id=leave_id,
            actor=request.user,
        )

        LeaveApplicationService.execute(command)

This keeps business rules independent from HTTP.


Clean Architecture

The internal structure can follow:

code
Interface Layer
       │
       ▼
Application Layer
       │
       ▼
Domain Layer
       │
       ▼
Infrastructure Layer

Example:

code
apps/payroll/

├── api/
│   ├── serializers.py
│   ├── views.py
│   └── urls.py
│
├── application/
│   ├── commands.py
│   ├── handlers.py
│   └── services.py
│
├── domain/
│   ├── entities.py
│   ├── value_objects.py
│   ├── policies.py
│   └── events.py
│
├── infrastructure/
│   ├── repositories.py
│   ├── models.py
│   └── providers.py
│
└── tasks/
    └── payroll_tasks.py

The domain should not depend on Django HTTP concepts.


Database Architecture

PostgreSQL is the transactional source of truth.

The database should enforce important invariants.

Examples:

code
UNIQUE organization + employee_number

UNIQUE organization + payroll_period

UNIQUE attendance external_event_id

CHECK salary >= 0

CHECK effective_from <= effective_to

Application validation is useful.

Database constraints are mandatory for critical invariants.


Effective-Dated Data

Several HCM entities require temporal modeling.

Examples:

code
Employment Assignment
Compensation
Job Position
Manager Relationship
Benefits
Leave Policy

A historical query should be possible:

code
What department did employee X belong to
on March 15?

Conceptually:

code
effective_from <= target_date
AND (
    effective_to IS NULL
    OR effective_to > target_date
)

This enables accurate historical reporting.


Temporal Consistency

Consider:

code
Promotion effective: July 1
Payroll period: June 1 → June 30

Payroll must use the compensation before July 1.

For:

code
Payroll period: July 1 → July 31

the new compensation applies.

Therefore payroll calculations should resolve effective-dated records against the payroll period rather than simply reading the employee's current state.


Data Ownership Matrix

DomainOwns
IdentityUsers, authentication
OrganizationTenant structure
EmployeeEmployee identity and lifecycle
RecruitmentCandidates and hiring pipeline
CompensationSalary and compensation versions
AttendanceRaw attendance events
LeaveLeave policies and balances
PayrollPayroll runs and payroll results
PerformanceGoals and reviews
WorkflowApproval state
AuditImmutable audit events
AnalyticsDerived reporting projections

The principle is:

One domain owns the truth. Other domains consume it.


Cross-Domain Transaction Boundaries

Not every business operation should be one giant database transaction.

Example:

code
Approve Salary Change

The compensation change itself should be transactional.

Then:

code
CompensationChanged

is consumed asynchronously by:

code
Payroll
Analytics
Notification
Audit

This avoids distributed transactions across unrelated domains.


Strong vs Eventual Consistency

TalentFlow intentionally uses both.

Strong Consistency

Required for:

code
Payroll finalization
Leave balance deduction
Compensation version creation
Employee lifecycle transitions
Approval state changes
Tenant authorization

Eventual Consistency

Acceptable for:

code
Dashboards
Analytics
Notifications
Search indexes
Email
Reporting projections

The architecture does not try to make every part of the system strongly consistent.

It applies the strongest consistency model only where the business actually requires it.


Failure Handling

Production systems fail.

TalentFlow therefore assumes:

code
Workers crash
Requests retry
Webhooks duplicate
External APIs timeout
Messages arrive late
Redis becomes unavailable
Database connections fail

The architecture responds with:

code
Idempotency
+
Transactions
+
Retries
+
Outbox
+
Audit
+
Reconciliation

rather than assuming successful execution.


Example: Employee Termination

A termination is not:

code
employee.status = "terminated"

The actual workflow is:

code
Termination Requested
        │
        ▼
Validate Authorization
        │
        ▼
Validate Employment State
        │
        ▼
Approval Workflow
        │
        ▼
Termination Approved
        │
        ▼
Transactional State Change
        │
        ├── Employee Lifecycle Event
        ├── Employment End Date
        └── Outbox Event
                │
                ▼
        Event Consumers
          ├── Payroll
          ├── Leave
          ├── Access Control
          ├── Benefits
          ├── Notification
          └── Analytics

Each consumer reacts independently.


Example: Promotion

code
Manager Requests Promotion
        │
        ▼
Approval Workflow
        │
        ▼
Approved
        │
        ▼
Create New Employment Assignment
        │
        ▼
Create New Compensation Version
        │
        ▼
Publish PromotionApproved
        │
        ├──► Payroll
        ├──► Analytics
        ├──► Employee Portal
        └──► Audit

The old assignment remains historically available.


Example: Leave Approval

code
Employee submits leave
        │
        ▼
Leave Policy Validation
        │
        ▼
Balance Validation
        │
        ▼
Manager Approval
        │
        ▼
HR Approval (if required)
        │
        ▼
Transaction
   ├── Leave Request = Approved
   └── Leave Ledger Entry = -N days
        │
        ▼
LeaveApproved Event
        │
        ├──► Attendance
        ├──► Payroll
        ├──► Calendar
        └──► Notification

The balance deduction and approval state must happen atomically.


Example: Payroll Run

code
Create Payroll Period
        │
        ▼
Lock Payroll Run
        │
        ▼
Resolve Eligible Employees
        │
        ▼
Create Snapshots
        │
        ▼
Calculate Payroll
        │
        ├── Compensation
        ├── Attendance
        ├── Leave
        ├── Benefits
        ├── Deductions
        └── Tax Rules
        │
        ▼
Generate Payroll Line Items
        │
        ▼
Validation
        │
        ▼
Payroll Review
        │
        ▼
Approval
        │
        ▼
Finalization
        │
        ▼
Payment Instructions
        │
        ▼
Provider
        │
        ▼
Reconciliation

Every stage has an explicit state.


Reporting Architecture

Operational reporting should not create heavy queries against transactional tables.

code
Transactional Database
        │
        ▼
Domain Events
        │
        ▼
Projection Workers
        │
        ▼
Analytics Read Models
        │
        ▼
Reporting API
        │
        ▼
Dashboard

Examples:

code
Headcount by Department
Payroll Cost by Branch
Turnover Rate
Leave Utilization
Recruitment Conversion
Average Time to Hire
Attendance Trends
Performance Distribution

Workforce Analytics Model

Analytics can maintain specialized projections.

code
EmployeeSnapshot
PayrollSnapshot
AttendanceSnapshot
RecruitmentSnapshot
LeaveSnapshot
PerformanceSnapshot

This avoids forcing one normalized operational schema to serve every analytical query.


Search Architecture

Employee search can eventually become expensive when organizations contain hundreds of thousands of employees.

Instead of forcing PostgreSQL to support every fuzzy search workload:

code
Employee Database
      │
      ▼
Search Projection
      │
      ▼
Search Index

The search index remains derived data.

PostgreSQL remains the source of truth.


Notification Architecture

Notifications are asynchronous.

code
Domain Event
     │
     ▼
Notification Consumer
     │
     ├── Email
     ├── Push
     ├── In-App
     └── SMS

Example:

code
LeaveApproved
      │
      ▼
Notification Service
      │
      ├── Employee notification
      └── Manager notification

Notification failures should never roll back payroll or leave approval.


Security Architecture

Security exists at multiple layers.

code
Authentication
      │
      ▼
Tenant Isolation
      │
      ▼
RBAC
      │
      ▼
Resource Authorization
      │
      ▼
Field-Level Protection
      │
      ▼
Audit

Sensitive workforce information includes:

code
Salary
Bank Information
Government IDs
Tax Information
Performance Reviews
Personal Contact Information

Access to these fields should be explicitly governed.


API Security

JWT authentication establishes identity.

Authorization establishes what that identity can do.

These are separate concerns.

code
JWT
 │
 └── Who are you?

RBAC / Policy
 │
 └── What are you allowed to do?

Tenant Scope
 │
 └── Which organization's data can you access?

Resource Policy
 │
 └── Which specific resource can you access?

Authentication alone must never be treated as authorization.


Audit-Ready Payroll

Payroll requires especially strong auditability.

A payroll result should be explainable through:

code
Payroll Run
   │
   ├── Employee Snapshot
   ├── Compensation Snapshot
   ├── Attendance Snapshot
   ├── Leave Snapshot
   ├── Benefit Snapshot
   ├── Deduction Snapshot
   └── Calculation Line Items

Therefore an auditor can answer:

code
Why did this employee receive this amount?

without relying on today's employee profile.


Architecture Evolution

The platform can evolve incrementally.

code
Phase 1
Basic Employee Management
        │
        ▼
Phase 2
Recruitment + Onboarding
        │
        ▼
Phase 3
Attendance + Leave
        │
        ▼
Phase 4
Compensation + Payroll
        │
        ▼
Phase 5
Workflow + Approval Engine
        │
        ▼
Phase 6
Domain Events + Outbox
        │
        ▼
Phase 7
CQRS Analytics
        │
        ▼
Phase 8
External Payroll / Banking Integrations
        │
        ▼
Phase 9
Advanced Workforce Intelligence

The important point is that domain boundaries should exist early even if the initial deployment is a modular Django monolith.


Modular Monolith First

The platform does not need microservices on day one.

A strong starting architecture is:

code
                    Django Application
                           │
       ┌───────────────────┼───────────────────┐
       │                   │                   │
       ▼                   ▼                   ▼
   Employee            Payroll             Recruitment
       │                   │                   │
       ▼                   ▼                   ▼
   Attendance             Leave           Performance
       │                   │                   │
       └───────────────────┼───────────────────┘
                           │
                           ▼
                     PostgreSQL

Each module maintains clear boundaries.

As scale increases, selected domains can be extracted.

For example:

code
Django Modular Monolith
        │
        ▼
Payroll becomes independent service
        │
        ▼
Attendance ingestion becomes independent service
        │
        ▼
Analytics becomes independent pipeline

The architecture therefore scales organizationally before it scales physically.


What I Would Build Today

If I were building TalentFlow today, I would start with:

  • Django + Django REST Framework for the core API and domain application
  • PostgreSQL as the transactional system of record
  • Redis for caching, rate limiting, coordination, and ephemeral state
  • Celery for asynchronous workflows and scheduled processing
  • JWT authentication for API access
  • Modular Django domains rather than premature microservices
  • Explicit domain services for critical business operations
  • Effective-dated employment and compensation records
  • Immutable payroll snapshots and finalized payroll results
  • Outbox pattern for reliable domain event publication
  • Idempotent consumers for all asynchronous events
  • CQRS read models for workforce analytics
  • Reusable workflow engine for approvals
  • Append-only audit events for governance

What I Would Avoid Initially

I would explicitly avoid:

  • A giant Employee model containing every HR concern
  • Direct cross-domain database writes
  • A mutable salary field as the only compensation history
  • A mutable leave balance with no ledger
  • Payroll calculated directly from today's employee state
  • Finalized payroll records that remain editable
  • Business logic inside Django views
  • Using Redis as the source of truth
  • Making every operation asynchronous
  • Introducing microservices before domain boundaries are proven
  • Running heavy workforce analytics directly against operational tables
  • Treating authentication as authorization
  • Relying only on application validation for financial invariants

The goal is not to make the architecture complicated.

The goal is to make the business invariants impossible to accidentally violate.


Critical Invariants

TalentFlow should explicitly protect invariants such as:

code
Employee belongs to exactly one tenant context.

Payroll cannot be finalized twice.

Finalized payroll cannot be mutated.

Leave cannot consume more balance than policy allows.

Attendance events cannot be duplicated.

Compensation periods cannot overlap incorrectly.

A terminated employee cannot receive normal future payroll.

Unauthorized users cannot cross tenant boundaries.

Approval transitions must follow the workflow state machine.

Domain events must not disappear after successful transactions.

These invariants are more important than the framework being used.


Production Reliability Model

The complete reliability model looks like:

code
                 ┌───────────────────────┐
                 │     User Command      │
                 └───────────┬───────────┘
                             │
                             ▼
                    Validation + RBAC
                             │
                             ▼
                    Domain Operation
                             │
                             ▼
                  PostgreSQL Transaction
                       │           │
                       │           └──────► Outbox Event
                       │
                       ▼
                  Commit / Rollback
                             │
                             ▼
                      Async Consumers
                       │      │      │
                       ▼      ▼      ▼
                    Payroll Analytics Notification
                       │
                       ▼
                    Retry / Idempotency
                       │
                       ▼
                    Audit / Monitoring

This creates a system where:

code
Synchronous path
=
business correctness

Asynchronous path
=
propagation and scale

That distinction is fundamental.


Final Architecture

The complete TalentFlow architecture can be summarized as:

code
                         ┌────────────────────────────┐
                         │      Web / Mobile / ESS    │
                         └─────────────┬──────────────┘
                                       │
                                       ▼
                         ┌────────────────────────────┐
                         │       API / Auth Layer     │
                         │ JWT + Tenant + RBAC        │
                         └─────────────┬──────────────┘
                                       │
          ┌────────────────────────────┼─────────────────────────────┐
          │                            │                             │
          ▼                            ▼                             ▼
 ┌─────────────────┐          ┌─────────────────┐          ┌─────────────────┐
 │ Employee Domain │          │ Recruitment     │          │ Payroll Domain  │
 │                 │          │                 │          │                 │
 │ Lifecycle       │          │ Candidates      │          │ Calculation     │
 │ Employment      │          │ Jobs            │          │ Snapshots       │
 │ Assignments     │          │ Interviews      │          │ Finalization    │
 └────────┬────────┘          └────────┬────────┘          └────────┬────────┘
          │                            │                            │
          ▼                            ▼                            ▼
 ┌─────────────────┐          ┌─────────────────┐          ┌─────────────────┐
 │ Attendance      │          │ Leave           │          │ Performance     │
 │                 │          │                 │          │                 │
 │ Raw Events      │          │ Policies        │          │ Goals           │
 │ Shifts          │          │ Balances        │          │ Reviews         │
 │ Timesheets      │          │ Approvals       │          │ Cycles          │
 └────────┬────────┘          └────────┬────────┘          └────────┬────────┘
          │                            │                            │
          └────────────────────────────┼────────────────────────────┘
                                       │
                                       ▼
                         ┌────────────────────────────┐
                         │      Domain Event Layer    │
                         │                            │
                         │ Outbox + Event Consumers   │
                         └─────────────┬──────────────┘
                                       │
                  ┌────────────────────┼────────────────────┐
                  │                    │                    │
                  ▼                    ▼                    ▼
          ┌───────────────┐    ┌───────────────┐    ┌───────────────┐
          │ PostgreSQL    │    │ Redis         │    │ Celery        │
          │               │    │               │    │               │
          │ Transactions  │    │ Cache         │    │ Async Jobs    │
          │ Payroll       │    │ Locks         │    │ Events        │
          │ Employees     │    │ Rate Limits   │    │ Notifications │
          │ Audit         │    │ Ephemeral     │    │ Analytics     │
          └───────┬───────┘    └───────────────┘    └───────┬───────┘
                  │                                          │
                  ▼                                          ▼
          ┌───────────────┐                         ┌──────────────────┐
          │ CQRS Read     │                         │ External Systems │
          │ Models        │                         │                  │
          │               │                         │ Banking          │
          │ Workforce     │                         │ Tax              │
          │ Analytics     │                         │ Email            │
          │ Dashboards    │                         │ Attendance       │
          └───────────────┘                         └──────────────────┘

The architectural philosophy is simple:

code
Employee state is temporal.
Payroll state is immutable after finalization.
Leave is ledger-driven.
Attendance begins with immutable events.
Approvals are state machines.
Authorization is tenant + scope aware.
Business operations are transactional.
Cross-domain communication is event-driven.
Events are published reliably through an outbox.
Async consumers are idempotent.
Analytics are derived through CQRS projections.
Audit history is append-only.
Redis accelerates the system but never owns the truth.

TalentFlow HCM therefore becomes more than an HR management application.

It becomes a transactional workforce operating system where employee identity, organizational structure, compensation, attendance, leave, performance, payroll, approvals, and analytics are connected through explicit domain boundaries and governed state transitions.

The important architectural decision is not Django, PostgreSQL, Redis, or Celery individually.

It is the decision to model workforce operations as governed business state and events rather than mutable CRUD records.

That is what allows the same architecture to remain correct when the organization grows from:

code
50 employees
     │
     ▼
500 employees
     │
     ▼
5,000 employees
     │
     ▼
50,000+ employees

without turning payroll, employee history, approvals, or workforce reporting into a collection of reconciliation problems.

status: blueprint_loaded

$ ls related_articles

status: end_of_file

Related Blueprints

Continue exploring related architecture patterns.

TalentFlow HCM (Human Capital Management Platform)

Enterprise-grade human capital management platform designed to unify employee lifecycle management, payroll operations, recruitment workflows, attendance tracking, performance management, and workforce analytics within a secure multi-tenant architecture.

01WebSocket-first real-time communication
02Versioned contracts and acceptance workflows
03Double-entry escrow and wallet ledger
04Atomic milestone state transitions
05Transactional outbox and idempotent events
06Dispute-driven fund freezing
07CQRS read models for messaging and transactions

Nexus SCM (Supply Chain & Warehouse Management Platform)

Enterprise-grade supply chain management platform designed to unify procurement, warehouse operations, inventory control, distribution, supplier collaboration, and logistics workflows through a ledger-first, event-driven, multi-tenant architecture.

01Ledger-first inventory architecture
02Warehouse-scoped stock ownership
03Transactional outbox for reliable event publishing
04Event-driven warehouse workflows
05CQRS read models for operational analytics
06Idempotent distributed event processing
07Multi-echelon inventory visibility
08Automated replenishment and forecasting

FinCore Treasury (Financial Operations & Treasury Platform)

Architecture blueprint for a multi-tenant treasury platform covering bank connectivity, cash management, payment orchestration, reconciliation, approval governance, immutable financial accounting, and event-driven financial operations.

01Multi-bank cash visibility
02Event-driven payment orchestration
03Automated reconciliation engine
04Tiered approval governance
05Immutable financial ledger