Skip to content

Payroll split — current, planned, migrating

One system three times: Payroll inside one application on a shared database, Payroll as its own service with its own database, and the state in between with @migration_target / @planned.

View the source on GitHub

System view
System view
Org view
Org view
// payroll-split/index.krs: the CURRENT architecture.
// The second subject of the first-run guide (#3601). Compare it with planned.krs
// (the target) and migrating.krs (the state in between).
//
// Attendance and Payroll are separate domains, but both live in one
// Workforce Application and share one Workforce Database. The Payroll Team
// owns the Payroll domain without owning any service.
system Workforce {
label "Workforce Platform"
user Employee [human] {
label "Employee"
description "Records hours and checks payslips"
}
user PayrollStaff [human] {
label "Payroll staff"
description "Reviews pay runs and transfers"
}
service WorkforceApp {
label "Workforce Application"
description "One deployable that holds attendance and payroll"
domain Attendance {
label "Attendance"
usecase RecordHours {
label "Record hours"
resource WorkforceDB.Timesheets { operations create }
}
usecase ApproveTimesheet {
label "Approve a timesheet"
resource WorkforceDB.Timesheets { operations update }
}
Attendance -> Payroll "Hand over closed timesheets"
}
domain Payroll {
label "Payroll"
usecase CalculatePay {
label "Calculate pay"
resource WorkforceDB.Payslips { operations create }
resource WorkforceDB.Timesheets { operations read }
}
usecase TransferPay {
label "Transfer pay"
resource WorkforceDB.Transfers { operations create }
}
}
}
service ReminderWorker {
label "Reminder Worker"
description "Sends timesheet reminders in the background"
domain Reminders {
label "Reminders"
usecase SendReminder {
label "Send a reminder"
resource WorkforceDB.ReminderQueue { operations update }
}
}
}
service BankTransfer [external] {
label "Bank Transfer"
description "Salary payments, run by a bank"
}
database WorkforceDB {
label "Workforce Database"
description "Shared by every part of the platform"
table Timesheets { label "Timesheets" }
table Payslips { label "Payslips" }
table Transfers { label "Transfers" }
table ReminderQueue { label "Reminder queue" }
}
Employee -> WorkforceApp "Record hours and check payslips"
PayrollStaff -> WorkforceApp "Review pay runs"
WorkforceApp -> ReminderWorker "Schedule reminders"
WorkforceApp -> BankTransfer "Send transfers"
}
organization Corporate {
label "Corporate Engineering"
team workforce {
label "Workforce Team"
description "Builds the employee portal and the attendance flow"
owns WorkforceApp
owns Attendance
}
team payroll {
label "Payroll Team"
description "Owns the pay rules, but ships them inside the Workforce Application"
owns Payroll
}
team platform {
label "Platform Team"
description "Runs the shared database and the background workers"
owns ReminderWorker
owns Reminders
}
}
System view
System view
Org view
Org view
// payroll-split/planned.krs: the TARGET architecture.
// Payroll runs as its own service with its own database, and the team
// that owns the pay rules owns the service that runs them.
// Compare it with index.krs (the current architecture).
system Workforce {
label "Workforce Platform"
user Employee [human] {
label "Employee"
description "Records hours and checks payslips"
}
user PayrollStaff [human] {
label "Payroll staff"
description "Reviews pay runs and transfers"
}
service WorkforceApp {
label "Workforce Application"
description "Employee portal and attendance"
domain Attendance {
label "Attendance"
usecase RecordHours {
label "Record hours"
resource WorkforceDB.Timesheets { operations create }
}
usecase ApproveTimesheet {
label "Approve a timesheet"
resource WorkforceDB.Timesheets { operations update }
}
}
}
service PayrollService {
label "Payroll Service"
description "Pay runs and transfers, own release cycle"
domain Payroll {
label "Payroll"
usecase CalculatePay {
label "Calculate pay"
resource PayrollDB.Payslips { operations create }
}
usecase TransferPay {
label "Transfer pay"
resource PayrollDB.Transfers { operations create }
}
}
}
service ReminderWorker {
label "Reminder Worker"
description "Sends timesheet reminders in the background"
domain Reminders {
label "Reminders"
usecase SendReminder {
label "Send a reminder"
resource WorkforceDB.ReminderQueue { operations update }
}
}
}
service BankTransfer [external] {
label "Bank Transfer"
description "Salary payments, run by a bank"
}
database WorkforceDB {
label "Workforce Database"
description "Timesheets and reminders"
table Timesheets { label "Timesheets" }
table ReminderQueue { label "Reminder queue" }
}
database PayrollDB {
label "Payroll Database"
description "Owned by payroll alone"
table Payslips { label "Payslips" }
table Transfers { label "Transfers" }
}
Employee -> WorkforceApp "Record hours"
Employee -> PayrollService "Check payslips"
PayrollStaff -> PayrollService "Review pay runs"
WorkforceApp -> PayrollService "Hand over closed timesheets"
WorkforceApp -> ReminderWorker "Schedule reminders"
PayrollService -> BankTransfer "Send transfers"
}
organization Corporate {
label "Corporate Engineering"
team workforce {
label "Workforce Team"
description "Builds the employee portal and the attendance flow"
owns WorkforceApp
owns Attendance
}
team payroll {
label "Payroll Team"
description "Owns the pay rules and the service that runs them"
owns PayrollService
owns Payroll
}
team platform {
label "Platform Team"
description "Runs the shared database and the background workers"
owns ReminderWorker
owns Reminders
}
}
System view
System view
Org view
Org view
// payroll-split/migrating.krs: the state IN BETWEEN.
// Payroll Service is running, but it still reads and writes the shared
// Workforce Database: its own Payroll Database is planned, not built.
// Moving the data is the step that decides when the split is done.
system Workforce {
label "Workforce Platform"
user Employee [human] {
label "Employee"
description "Records hours and checks payslips"
}
user PayrollStaff [human] {
label "Payroll staff"
description "Reviews pay runs and transfers"
}
service WorkforceApp {
label "Workforce Application"
description "Employee portal and attendance"
domain Attendance {
label "Attendance"
usecase RecordHours {
label "Record hours"
resource WorkforceDB.Timesheets { operations create }
}
usecase ApproveTimesheet {
label "Approve a timesheet"
resource WorkforceDB.Timesheets { operations update }
}
}
}
service PayrollService @migration_target(from: WorkforceApp) {
label "Payroll Service"
description "Split out, still on the shared database"
domain Payroll {
label "Payroll"
usecase CalculatePay {
label "Calculate pay"
resource WorkforceDB.Payslips { operations create }
resource WorkforceDB.Timesheets { operations read }
}
usecase TransferPay {
label "Transfer pay"
resource WorkforceDB.Transfers { operations create }
}
}
}
service ReminderWorker {
label "Reminder Worker"
description "Sends timesheet reminders in the background"
domain Reminders {
label "Reminders"
usecase SendReminder {
label "Send a reminder"
resource WorkforceDB.ReminderQueue { operations update }
}
}
}
service BankTransfer [external] {
label "Bank Transfer"
description "Salary payments, run by a bank"
}
database WorkforceDB {
label "Workforce Database"
description "Payroll tables have not moved yet"
table Timesheets { label "Timesheets" }
table Payslips @deprecated { label "Payslips" }
table Transfers @deprecated { label "Transfers" }
table ReminderQueue { label "Reminder queue" }
}
database PayrollDB @planned {
label "Payroll Database"
description "Where the payroll tables will move"
table Payslips { label "Payslips" }
table Transfers { label "Transfers" }
}
Employee -> WorkforceApp "Record hours"
Employee -> PayrollService "Check payslips"
PayrollStaff -> PayrollService "Review pay runs"
WorkforceApp -> PayrollService "Hand over closed timesheets"
WorkforceApp -> ReminderWorker "Schedule reminders"
PayrollService -> BankTransfer "Send transfers"
}
organization Corporate {
label "Corporate Engineering"
team workforce {
label "Workforce Team"
description "Builds the employee portal and the attendance flow"
owns WorkforceApp
owns Attendance
}
team payroll {
label "Payroll Team"
description "Runs Payroll Service; depends on the Platform Team for its data until the move"
owns PayrollService
owns Payroll
}
team platform {
label "Platform Team"
description "Runs the shared database and the background workers"
owns ReminderWorker
owns Reminders
}
}

© 2026 Hiroki Kondo · Licensed underApache-2.0

Built with Cloudflare