End-of-Day, Beginning-of-Day, and Operational Calendar
Learn Java Core Banking System - Part 021
End-of-Day, Beginning-of-Day, operational calendar, business date control, batch orchestration, idempotent rerun, restartability, control totals, and production-grade banking operations.
Part 021 — End-of-Day, Beginning-of-Day, and Operational Calendar
Core banking tidak hanya berjalan berdasarkan jam server. Core banking berjalan berdasarkan business date, operational calendar, cutoff, ledger closure, dan control evidence.
Part ini membahas End-of-Day (EOD) dan Beginning-of-Day (BOD) sebagai control system, bukan hanya batch job. Di banking, EOD/BOD adalah mekanisme untuk mengubah kumpulan transaksi harian menjadi posisi resmi: balance, accrual, fee, GL handoff, statement, regulatory extract, dan operational evidence.
Kita tidak akan mengulang detail Java batch, concurrency, observability, SQL, atau error handling yang sudah dipelajari di seri lain. Fokus part ini adalah domain architecture: bagaimana Java core banking system harus memodelkan business date, calendar, cutoff, batch lifecycle, restartability, rerun safety, dan operational evidence.
1. Mengapa EOD/BOD Itu Fundamental?
Banyak engineer melihat EOD sebagai:
"Job malam untuk menghitung bunga dan generate report."
Itu terlalu dangkal.
Dalam core banking, EOD adalah ritual formal penutupan posisi operasional. Setelah EOD selesai, sistem harus bisa menjawab:
- transaksi apa saja yang masuk ke business date tertentu;
- balance resmi setiap rekening pada akhir hari;
- accrual, fee, tax, dan maturity apa yang dihitung;
- suspense, clearing, settlement, dan GL movement apa yang terjadi;
- exception apa yang masih terbuka;
- apakah total subledger cocok dengan GL interface;
- report dan extract apa yang valid untuk audit/regulator;
- apakah proses bisa dibuktikan, diulang, dan dijelaskan.
BOD adalah fase pembukaan business date baru. Setelah BOD selesai, sistem harus tahu:
- business date aktif;
- cutoff window baru;
- produk, rate, calendar, dan parameter versi mana yang berlaku;
- account mana yang berubah status karena maturity/dormancy/restriction;
- scheduled instruction mana yang eligible dieksekusi;
- apakah sistem siap menerima transaksi channel.
Mental model paling penting:
EOD closes truth for one business date.
BOD opens eligibility for the next business date.
2. Business Date vs System Date vs Value Date
Core banking harus membedakan minimal tiga jenis tanggal.
| Tanggal | Makna | Contoh |
|---|---|---|
systemDateTime | Waktu teknis ketika event diproses | 2026-06-28T23:10:03+07:00 |
businessDate | Tanggal operasional bank yang sedang aktif | 2026-06-28 |
valueDate | Tanggal efektif ekonomi/akuntansi transaksi | 2026-06-27 |
postingDate | Tanggal transaksi diposting ke ledger | 2026-06-28 |
settlementDate | Tanggal settlement eksternal/final | 2026-06-30 |
Kesalahan umum adalah memakai LocalDate.now() langsung di domain logic. Itu berbahaya.
2.1 Anti-pattern: LocalDate.now() di domain layer
public void post(TransactionCommand command) {
LocalDate businessDate = LocalDate.now(); // salah untuk core banking
// ...
}
Masalah:
- tidak bisa replay;
- tidak bisa simulasi EOD;
- tidak bisa deterministic testing;
- salah saat EOD masih berjalan melewati tengah malam;
- salah untuk branch dengan calendar berbeda;
- salah untuk backdated transaction;
- susah audit.
2.2 Pattern: Business Clock
public interface BusinessClock {
LocalDate currentBusinessDate(BankUnitId bankUnitId);
ZonedDateTime currentSystemDateTime();
BusinessWindow currentWindow(BankUnitId bankUnitId);
}
Domain service harus menerima business date dari operational date service, bukan dari wall-clock.
public final class PostingApplicationService {
private final BusinessClock businessClock;
private final PostingEngine postingEngine;
public PostingResult post(TransferCommand command) {
BusinessContext context = new BusinessContext(
businessClock.currentBusinessDate(command.bankUnitId()),
businessClock.currentSystemDateTime(),
command.channel(),
command.correlationId()
);
return postingEngine.post(command, context);
}
}
3. Operational Calendar
Operational calendar bukan hanya daftar hari libur. Ia adalah domain object yang menentukan apakah bank bisa menerima, memproses, men-settle, atau menutup transaksi.
3.1 Calendar yang lazim dibutuhkan
| Calendar | Dipakai untuk |
|---|---|
| Bank calendar | business date bank secara umum |
| Branch calendar | cabang dengan hari operasional khusus |
| Currency calendar | settlement mata uang tertentu |
| Product calendar | rule produk seperti maturity dan accrual |
| Clearing calendar | rail payment eksternal |
| Market calendar | rate, treasury, securities, FX |
| Regulatory calendar | reporting deadline |
3.2 Calendar bukan boolean sederhana
Jangan hanya punya:
boolean isHoliday(LocalDate date);
Lebih baik modelkan keputusan operasional:
public record BusinessDayDecision(
LocalDate date,
boolean bankingOpen,
boolean customerTransactionAllowed,
boolean internalPostingAllowed,
boolean clearingAllowed,
boolean settlementAllowed,
boolean eodAllowed,
String reasonCode
) {}
Karena hari yang sama bisa:
- tutup untuk customer transaction;
- tetap boleh untuk internal accrual;
- tidak boleh untuk clearing;
- tetap boleh untuk regulatory reporting;
- memiliki cutoff lebih awal.
4. Cutoff Window
Cutoff adalah batas operasional untuk menentukan transaksi masuk business date mana.
Contoh:
Business date: 2026-06-28
Customer transfer cutoff: 18:00
Teller cutoff: 16:00
Internal posting cutoff: 22:00
EOD starts: 23:00
Transaksi pukul 19:00 mungkin masih diterima channel, tetapi dianggap business date berikutnya, atau masuk pending queue.
4.1 Model cutoff
public record CutoffPolicy(
BankUnitId bankUnitId,
Channel channel,
TransactionType transactionType,
LocalTime cutoffTime,
CutoffBehavior behavior
) {}
public enum CutoffBehavior {
ACCEPT_CURRENT_BUSINESS_DATE,
ACCEPT_NEXT_BUSINESS_DATE,
QUEUE_FOR_NEXT_BUSINESS_DATE,
REJECT,
REQUIRE_MANUAL_APPROVAL
}
4.2 Cutoff decision trace
Setiap keputusan cutoff harus menyimpan trace:
public record CutoffDecision(
LocalDate requestedBusinessDate,
LocalDate assignedBusinessDate,
ZonedDateTime receivedAt,
Channel channel,
TransactionType transactionType,
CutoffBehavior behavior,
String policyVersion,
String reasonCode
) {}
Ini penting untuk dispute:
"Saya transfer sebelum jam tutup, kenapa diproses besok?"
Jawaban sistem harus berbasis evidence, bukan asumsi.
5. EOD sebagai State Machine
EOD harus dimodelkan sebagai state machine eksplisit.
Setiap state harus punya:
- start time;
- end time;
- operator/system actor;
- input snapshot;
- output control totals;
- error summary;
- retry count;
- decision trail;
- approval jika manual override;
- hash/checksum jika perlu evidence kuat.
6. EOD Phases
EOD biasanya terdiri dari beberapa fase. Nama bisa berbeda antar bank, tetapi mental modelnya mirip.
6.1 Precheck
Precheck memastikan sistem boleh ditutup.
Contoh precheck:
| Check | Pertanyaan |
|---|---|
| Open batch check | Apakah ada batch upload belum selesai? |
| Pending posting check | Apakah ada accepted transaction belum diposting? |
| Unbalanced journal check | Apakah ada journal tidak balance? |
| Repair queue check | Apakah ada repair item blocker? |
| Suspense threshold check | Apakah suspense melebihi toleransi? |
| External file check | Apakah file clearing wajib sudah diterima? |
| GL mapping check | Apakah semua product/account punya mapping GL? |
| Calendar check | Apakah business date valid ditutup? |
| Parameter check | Apakah rate/product config untuk hari ini lengkap? |
Precheck harus membedakan:
- hard blocker: EOD tidak boleh lanjut;
- soft warning: EOD boleh lanjut dengan approval;
- informational: hanya dicatat.
public enum EodCheckSeverity {
BLOCKER,
WARNING_REQUIRES_APPROVAL,
INFO
}
public record EodPrecheckResult(
String checkCode,
EodCheckSeverity severity,
boolean passed,
long affectedCount,
BigDecimal affectedAmount,
String evidenceQueryRef,
String message
) {}
6.2 Transaction cutoff
Pada fase cutoff, sistem menentukan batas transaksi untuk business date.
Important invariant:
No new customer-originated transaction may be assigned to a business date after that date enters cutoff/freeze, unless explicitly allowed by controlled backdated process.
6.3 Posting freeze
Freeze bukan berarti semua posting berhenti. Biasanya customer-originated posting dihentikan, tetapi internal posting seperti accrual, fee, maturity, tax, GL settlement masih boleh.
Karena itu jangan modelkan freeze sebagai boolean global.
public record PostingPermission(
LocalDate businessDate,
PostingSource source,
TransactionType transactionType,
boolean allowed,
String reason
) {}
6.4 Accrual run
Accrual run menghitung pendapatan/beban bunga yang sudah terjadi secara ekonomi tetapi belum tentu jatuh tempo.
Control totals:
| Total | Makna |
|---|---|
| eligible accounts | jumlah rekening eligible |
| skipped accounts | rekening yang tidak dihitung dan alasannya |
| accrued debit amount | total debit accrual |
| accrued credit amount | total credit accrual |
| generated journal count | jumlah journal accrual |
| rounding adjustment | total rounding adjustment |
6.5 Fee run
Fee run menghitung biaya periodik atau event-derived fee yang belum diposting.
Contoh:
- monthly maintenance fee;
- minimum balance fee;
- dormant account fee;
- overdraft fee;
- statement fee;
- penalty fee.
Fee harus idempotent:
For same account + fee type + fee period + product version, fee posting must be generated at most once unless correction flow is used.
6.6 Maturity run
Maturity run menangani produk yang jatuh tempo:
- term deposit maturity;
- loan installment due;
- promotional rate expiry;
- guarantee expiry;
- blocked amount release;
- scheduled instruction activation.
6.7 GL extract
GL extract menghasilkan interface dari subledger core banking ke general ledger.
Control totals:
- number of accounting events;
- number of posting lines;
- debit total per GL account;
- credit total per GL account;
- net movement per GL account;
- suspense movement;
- unmatched mapping count.
6.8 Reconciliation run
Reconciliation run membandingkan:
- account balance vs ledger posting;
- subledger total vs GL interface;
- clearing file vs internal payment status;
- settlement account vs external statement;
- suspense aging;
- generated reports vs source totals.
Part 022 membahas ini lebih dalam.
6.9 Reporting run
Reporting bisa mencakup:
- customer statement;
- internal MIS;
- operational dashboard;
- regulatory extract;
- audit pack;
- risk data feed;
- downstream warehouse feed.
Reporting tidak boleh mengambil data “setengah matang”. Ia harus mengacu ke closed business date snapshot.
6.10 Business date closure
Setelah closure:
Business date is immutable except through controlled correction/backdated process.
Closure harus menghasilkan closure certificate:
public record EodClosureCertificate(
LocalDate businessDate,
String eodRunId,
Instant startedAt,
Instant completedAt,
EodStatus status,
List<ControlTotal> controlTotals,
List<ExceptionSummary> unresolvedExceptions,
String approvedBy,
String evidenceHash
) {}
7. BOD Phases
BOD membuka hari baru.
7.1 Advance business date
Business date tidak selalu previous + 1 day. Jika Sabtu/Minggu/libur, next business date bisa melompat.
public interface OperationalCalendar {
LocalDate nextBusinessDate(BankUnitId bankUnitId, LocalDate current);
BusinessDayDecision decisionFor(BankUnitId bankUnitId, LocalDate date);
}
7.2 Effective configuration loading
Pada BOD, sistem harus menetapkan versi konfigurasi yang berlaku:
- product parameters;
- interest rates;
- fee schedule;
- tax rates;
- GL mappings;
- cutoff windows;
- branch calendar;
- limit rules;
- AML/fraud routing policy;
- report templates.
Prinsip:
BOD should pin effective configuration versions for the business date.
Jika konfigurasi berubah di tengah hari, sistem harus jelas apakah perubahan berlaku:
- immediately;
- next business date;
- next cycle;
- next product version;
- only for new agreements.
7.3 Opening control totals
Opening control totals membuktikan bahwa posisi awal business date baru sama dengan posisi akhir business date sebelumnya.
openingBalance(2026-06-29) == closingBalance(2026-06-28)
Kecuali ada controlled opening adjustment.
8. EOD Job Design di Java
EOD bukan satu method besar.
Anti-pattern:
public void runEod() {
doPrecheck();
freeze();
calculateInterest();
chargeFees();
generateGl();
reconcile();
closeDate();
}
Masalah:
- tidak restartable;
- tidak observably staged;
- tidak punya control totals per step;
- sulit rerun satu fase;
- sulit audit;
- failure handling kacau;
- tidak bisa parallel secara aman;
- tidak bisa pause/resume;
- tidak ada approval gate.
8.1 Pattern: EOD Run + Step Execution
public record EodRunId(String value) {}
public record EodRun(
EodRunId id,
LocalDate businessDate,
EodStatus status,
Instant startedAt,
Instant completedAt,
List<EodStepExecution> steps
) {}
public record EodStepExecution(
String stepCode,
EodStepStatus status,
Instant startedAt,
Instant completedAt,
int attempt,
String inputFingerprint,
String outputFingerprint,
List<ControlTotal> controlTotals,
String errorCode
) {}
8.2 Step contract
public interface EodStep {
String code();
EodStepType type();
EodStepResult execute(EodStepContext context);
}
public record EodStepContext(
EodRunId runId,
LocalDate businessDate,
String stepExecutionId,
EodRerunMode rerunMode,
BusinessClock clock,
OperatorContext operator
) {}
8.3 Idempotent step design
Setiap step harus bisa menjawab:
- apakah step belum pernah jalan;
- apakah step sudah sukses;
- apakah output masih valid;
- apakah boleh rerun;
- apakah rerun harus reverse output sebelumnya;
- apakah ada partial output;
- apakah partial output bisa dilanjutkan.
Contoh idempotency key untuk fee EOD:
FEE_RUN:{businessDate}:{accountId}:{feeType}:{feePeriod}:{productVersion}
Contoh idempotency key untuk GL extract:
GL_EXTRACT:{businessDate}:{ledgerBook}:{glBatchType}:{extractVersion}
9. Restartability vs Rerunability
Dua konsep ini sering dicampur.
| Konsep | Makna |
|---|---|
| Restartability | Melanjutkan proses setelah gagal tanpa mulai dari nol |
| Rerunability | Menjalankan ulang proses untuk menghasilkan output yang konsisten |
| Reversibility | Membatalkan efek proses sebelumnya dengan posting koreksi |
| Rebuildability | Membangun ulang output dari source-of-truth |
9.1 Restartable
Jika job gagal di account ke-800.000 dari 1.000.000, sistem tidak boleh selalu ulang dari awal kalau mahal dan berisiko.
Gunakan checkpoint:
public record EodPartitionCheckpoint(
String stepExecutionId,
String partitionKey,
String lastProcessedKey,
long processedCount,
BigDecimal processedAmount,
Instant updatedAt
) {}
9.2 Rerunnable
Jika output EOD report corrupt, sistem bisa generate ulang dari closed snapshot tanpa mengubah ledger.
Read-only report generation should be rerunnable.
Posting-producing steps require stronger controls.
9.3 Reversible
Jika fee salah ter-posting, jangan delete journal. Buat reversal/adjustment.
Bad EOD posting is corrected by compensating accounting event, not by mutating historical posting lines.
10. Control Totals
Control total adalah alat utama untuk membuktikan bahwa batch tidak kehilangan atau menggandakan data.
10.1 Jenis control total
| Control total | Contoh |
|---|---|
| Count total | jumlah account eligible |
| Amount total | total debit/credit |
| Hash total | checksum account ids/output rows |
| Balance total | aggregate balance per product/currency |
| Exception total | jumlah failed/skipped/repaired |
| Reconciliation total | matched/unmatched amount |
| Aging total | suspense by age bucket |
10.2 Java model
public record ControlTotal(
String name,
ControlTotalType type,
String dimension,
BigDecimal amount,
Long count,
String currency,
String hash,
String sourceQueryRef
) {}
10.3 Example: accrual control totals
businessDate=2026-06-28
step=DAILY_INTEREST_ACCRUAL
currency=IDR
eligibleAccounts=1,250,000
processedAccounts=1,249,998
skippedAccounts=2
accrualDebitTotal=3,250,000,000.00
accrualCreditTotal=3,250,000,000.00
roundingAdjustment=12.00
journalCount=1,249,998
Jika debit dan credit tidak balance, EOD harus gagal.
11. Partitioning EOD Workload
EOD sering memproses jutaan rekening. Tapi partitioning harus menjaga invariant.
11.1 Partition key yang umum
| Workload | Partition key |
|---|---|
| Interest accrual | account range/product/currency |
| Fee run | product/fee type/account range |
| Statement generation | customer/account range |
| GL extract | ledger book/currency/accounting event range |
| Reconciliation | external file/statement date/account |
| Maturity | maturity date/product |
11.2 Jangan partition sembarangan
Jika satu account bisa diproses oleh dua partition secara bersamaan, bisa terjadi double posting.
Invariant:
A posting-producing EOD step must own a deterministic partition of accounts/events.
No two partitions may produce the same business effect.
11.3 Work leasing
public record EodWorkLease(
String leaseId,
String stepExecutionId,
String partitionKey,
String ownedByWorker,
Instant leasedUntil,
EodWorkStatus status
) {}
Worker harus memperbarui lease. Jika worker mati, lease expire dan partition bisa diambil worker lain dengan idempotency guard.
12. EOD and Online Transaction Interaction
Core banking modern sering harus 24/7. Tantangannya: EOD tetap perlu control, tetapi customer ingin transaksi kapan saja.
Ada beberapa model.
12.1 Hard downtime model
Channel ditutup selama EOD.
Kelebihan:
- sederhana;
- invariant mudah;
- reconciliation lebih mudah.
Kekurangan:
- buruk untuk digital banking;
- tidak cocok 24/7 payment;
- customer impact tinggi.
12.2 Queue-during-EOD model
Transaksi diterima tetapi tidak diposting sampai EOD selesai.
Kelebihan:
- user experience lebih baik;
- ledger closure aman.
Kekurangan:
- pending state kompleks;
- risiko timeout/dispute;
- channel harus memahami pending.
12.3 Dual business date model
Sistem menerima transaksi untuk next business date saat current business date sedang ditutup.
Kelebihan:
- mendukung operasi lebih panjang;
- EOD tidak selalu block channel.
Kekurangan:
- butuh strict business date assignment;
- reporting lebih kompleks;
- backdated/correction makin sensitif.
12.4 Near-real-time ledger with soft EOD
Ledger selalu online, EOD lebih banyak menghasilkan snapshot/report/control daripada menghentikan posting.
Kelebihan:
- cocok digital banking;
- downtime minimal.
Kekurangan:
- arsitektur lebih sulit;
- snapshot isolation harus kuat;
- reconciliation dan GL cut membutuhkan desain matang.
Tidak ada model universal. Pilihan tergantung produk, rail, regulasi, risk appetite, dan legacy constraint.
13. Business Date Assignment Pattern
Untuk sistem 24/7, business date assignment harus first-class.
public interface BusinessDateAssignmentService {
BusinessDateAssignment assign(TransactionIngress ingress);
}
public record BusinessDateAssignment(
LocalDate assignedBusinessDate,
AssignmentReason reason,
String cutoffPolicyVersion,
boolean requiresCustomerDisclosure,
boolean requiresManualApproval
) {}
Contoh:
ReceivedAt: 2026-06-28T18:30:00+07:00
Channel: MOBILE
Transaction: INTERBANK_TRANSFER
Cutoff: 17:00
Behavior: QUEUE_FOR_NEXT_BUSINESS_DATE
AssignedBusinessDate: 2026-06-29
Reason: AFTER_CUTOFF
14. Operational Evidence
EOD harus meninggalkan evidence. Tanpa evidence, sistem mungkin benar secara teknis tetapi lemah secara operasional.
Evidence minimum:
- run id;
- business date;
- step list;
- operator/system actor;
- input fingerprint;
- output fingerprint;
- control totals;
- exception list;
- approvals/overrides;
- report artifact references;
- source query references;
- config versions;
- release version;
- correlation IDs;
- incident references jika ada.
14.1 Evidence pack
public record EodEvidencePack(
EodRunId runId,
LocalDate businessDate,
String applicationVersion,
String configurationSnapshotId,
List<EodStepExecution> stepExecutions,
List<ControlTotal> finalControlTotals,
List<ApprovalEvidence> approvals,
List<ArtifactReference> generatedArtifacts,
String cryptographicDigest
) {}
Digest bukan pengganti audit, tetapi membantu mendeteksi perubahan artifact.
15. EOD Failure Handling
EOD failure harus diklasifikasi.
| Failure | Contoh | Response |
|---|---|---|
| Data quality failure | GL mapping missing | stop, fix config, rerun precheck |
| External dependency failure | clearing file belum datang | wait, manual override, partial close |
| Processing failure | worker crash | resume partition |
| Business invariant failure | unbalanced journal | stop, investigate |
| Reconciliation failure | external total mismatch | create break, maybe block close |
| Reporting failure | report generation error | rerun report, do not mutate ledger |
| Infrastructure failure | DB failover | resume from checkpoint |
15.1 Unknown outcome
Unknown outcome adalah kondisi ketika sistem tidak tahu apakah step berhasil atau gagal.
Contoh:
- worker timeout setelah commit;
- network failure saat publish event;
- DB connection dropped setelah server-side commit;
- file transfer status tidak jelas;
- external settlement acknowledgement terlambat.
Pattern:
Never guess. Reconcile state from durable source of truth.
Untuk posting-producing step, cek journal/idempotency table. Untuk file transfer, cek file registry dan external acknowledgement.
16. Partial Close dan Conditional Close
Beberapa bank memakai partial close:
- close deposit products dulu;
- loan EOD menyusul;
- GL extract split per ledger book;
- branch close per region;
- currency-specific close.
Ini bisa benar, tetapi harus eksplisit.
public record BusinessDateCloseScope(
LocalDate businessDate,
Optional<ProductFamily> productFamily,
Optional<BranchId> branchId,
Optional<Currency> currency,
Optional<LedgerBook> ledgerBook
) {}
Danger:
Partial close without explicit scope creates false confidence.
Dashboard harus menampilkan:
Business Date 2026-06-28:
- Deposits: CLOSED
- Loans: RECON_PENDING
- Payments: WAITING_EXTERNAL_STATEMENT
- GL: PARTIAL_EXTRACTED
- Reports: NOT_READY
17. EOD Database Design Sketch
Schema minimal:
CREATE TABLE eod_run (
id VARCHAR(64) PRIMARY KEY,
business_date DATE NOT NULL,
scope_type VARCHAR(50) NOT NULL,
scope_value VARCHAR(100),
status VARCHAR(40) NOT NULL,
started_at TIMESTAMP NOT NULL,
completed_at TIMESTAMP NULL,
application_version VARCHAR(100) NOT NULL,
config_snapshot_id VARCHAR(100) NOT NULL,
created_by VARCHAR(100) NOT NULL
);
CREATE TABLE eod_step_execution (
id VARCHAR(64) PRIMARY KEY,
eod_run_id VARCHAR(64) NOT NULL,
step_code VARCHAR(100) NOT NULL,
status VARCHAR(40) NOT NULL,
attempt INT NOT NULL,
started_at TIMESTAMP NOT NULL,
completed_at TIMESTAMP NULL,
input_fingerprint VARCHAR(256),
output_fingerprint VARCHAR(256),
error_code VARCHAR(100),
error_message VARCHAR(1000),
UNIQUE (eod_run_id, step_code, attempt)
);
CREATE TABLE eod_control_total (
id VARCHAR(64) PRIMARY KEY,
step_execution_id VARCHAR(64) NOT NULL,
name VARCHAR(100) NOT NULL,
dimension VARCHAR(200),
amount DECIMAL(38, 8),
count_value BIGINT,
currency CHAR(3),
hash_value VARCHAR(256),
source_query_ref VARCHAR(256)
);
CREATE TABLE eod_work_checkpoint (
id VARCHAR(64) PRIMARY KEY,
step_execution_id VARCHAR(64) NOT NULL,
partition_key VARCHAR(200) NOT NULL,
last_processed_key VARCHAR(200),
processed_count BIGINT NOT NULL,
processed_amount DECIMAL(38, 8),
status VARCHAR(40) NOT NULL,
updated_at TIMESTAMP NOT NULL,
UNIQUE(step_execution_id, partition_key)
);
18. EOD Orchestration Architecture
Orchestrator mengatur flow, tetapi business effect tetap di domain service masing-masing.
19. EOD Observability yang Bernilai Domain
Jangan hanya ukur CPU/memory/job duration. Ukur domain progress.
Metrics penting:
| Metric | Makna |
|---|---|
eod_step_duration_seconds | durasi step |
eod_processed_accounts_total | progress account |
eod_generated_journals_total | journal yang dibuat |
eod_skipped_accounts_total | account dilewati |
eod_control_total_amount | total amount per dimension |
eod_recon_breaks_total | jumlah break |
eod_unresolved_exceptions_total | exception terbuka |
eod_rerun_total | rerun frequency |
eod_manual_override_total | override frequency |
eod_sla_breach_total | breach terhadap operational SLA |
Trace harus menyimpan:
- eod run id;
- business date;
- step code;
- partition key;
- account/product/currency dimension bila aman;
- correlation id;
- control total reference.
20. Security dan Segregation of Duties
EOD operation harus punya kontrol akses.
Role umum:
| Role | Hak |
|---|---|
| EOD Operator | start/monitor routine EOD |
| EOD Supervisor | approve warning override |
| Finance Controller | approve GL-related exception |
| System Administrator | restart infrastructure, bukan approve business override |
| Auditor | read evidence only |
| Product Admin | manage configuration, bukan close business date |
Prinsip:
The person who changes financial configuration should not be the only person who approves EOD using that configuration.
21. EOD Anti-patterns
21.1 Giant batch script
Satu script besar yang melakukan semuanya membuat failure tidak bisa dikontrol.
21.2 Delete-and-regenerate posting
Menghapus posting EOD untuk rerun merusak audit trail.
21.3 Hidden manual SQL
Manual update tanpa evidence adalah operational debt besar.
21.4 Calendar hardcoded di code
Hari libur dan cutoff berubah. Calendar harus dikelola sebagai effective-dated reference data.
21.5 Report dari live table tanpa snapshot
Report bisa berubah saat query diulang.
21.6 EOD success hanya berdasarkan exit code
Exit code sukses tidak cukup. Harus ada control totals dan reconciliation evidence.
21.7 Menganggap EOD hanya masalah batch performance
Performance penting, tetapi correctness, restartability, dan evidence lebih penting.
22. Practical Design Checklist
Sebelum mengklaim EOD design production-ready, jawab ini:
- Apa sumber business date resmi?
- Siapa yang boleh advance business date?
- Apa perbedaan cutoff, freeze, close, dan open?
- Apakah setiap EOD step idempotent?
- Step mana yang read-only, posting-producing, file-producing, atau external-effect-producing?
- Apa idempotency key tiap step?
- Apa control totals tiap step?
- Apa hard blocker vs warning?
- Bagaimana rerun dilakukan?
- Bagaimana partial failure dipulihkan?
- Bagaimana unknown outcome diselesaikan?
- Apakah report memakai closed snapshot?
- Bagaimana GL extract dibuktikan balance?
- Bagaimana reconciliation break mempengaruhi closure?
- Siapa yang approve override?
- Apakah calendar effective-dated?
- Apakah product/rate config dipin untuk business date?
- Apakah opening balance hari baru cocok dengan closing balance hari sebelumnya?
- Apakah audit bisa melihat evidence pack tanpa query manual?
- Apakah channel memahami pending/after-cutoff semantics?
23. Deliberate Practice
Latihan 1 — Business Date Assignment
Desain fungsi yang menerima:
- timestamp transaksi;
- channel;
- transaction type;
- branch;
- cutoff policy;
- current EOD state.
Output:
- assigned business date;
- accept/queue/reject;
- reason code;
- customer disclosure flag.
Latihan 2 — EOD Precheck Matrix
Buat minimal 20 precheck untuk deposit + loan + payment. Klasifikasikan menjadi:
- blocker;
- warning requires approval;
- info.
Latihan 3 — Idempotent Fee Run
Rancang table dan idempotency key agar monthly maintenance fee tidak bisa ter-posting dua kali untuk account, fee period, dan product version yang sama.
Latihan 4 — Restartable Accrual
Rancang checkpoint untuk accrual 10 juta account. Jelaskan bagaimana sistem melanjutkan setelah worker crash.
Latihan 5 — EOD Evidence Pack
Buat struktur evidence pack untuk auditor yang bisa menjawab:
"Bagaimana kita tahu saldo akhir 2026-06-28 benar?"
24. Ringkasan Mental Model
EOD/BOD bukan batch administratif. Ia adalah operational truth boundary.
Pegang invariant ini:
A business date is not truly closed until its postings, controls, reconciliations, extracts, reports, and exceptions are explicitly known.
Dan:
A business date is not safely opened until its opening state, effective configuration, calendar, posting windows, and scheduled obligations are explicitly established.
Engineer top-tier tidak hanya bertanya:
"Job EOD jalan berapa lama?"
Ia bertanya:
- apa yang sedang ditutup;
- apa yang boleh berubah saat ditutup;
- apa evidence bahwa proses benar;
- apa yang terjadi jika gagal di tengah;
- apa yang bisa di-rerun tanpa efek samping;
- apa yang harus dikoreksi, bukan dihapus;
- bagaimana auditor/regulator/operator memahami hasilnya.
25. Referensi
- FFIEC, Architecture, Infrastructure, and Operations Booklet, Information Technology Examination Handbook.
- Basel Committee on Banking Supervision, BCBS 239: Principles for effective risk data aggregation and risk reporting.
- CPMI-IOSCO, Principles for financial market infrastructures.
- ISO 20022, Message Definitions: Bank-to-Customer Cash Management.
- OpenTelemetry, Observability framework for traces, metrics, and logs.
You just completed lesson 21 in deepen practice. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.
Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.