Maker-Checker flows, are processes where every change done by a backoffice user (maker) needs to be approved by their supervisor (checker).

Requirements

Making

This allows initializing pending changes of customer/account/card/… data

If there is already a pending change on some entity, another pineding change cannot be introduced there.

Checking

This allows such change to be

  • rejected, the flow ends

  • approved, the change is applied in the target MS, and the flow ends

    • or change change fails to be applied, and the flower ends

  • All pending changes are automatically rejected at the end of the day

Viewing

The change(s) need to be queriable by

  • the customer ID

  • the maker ID (so a maker can see what changes they made)

  • the checker ID (so a checker can see all the tasks they can check)

  • change status (so that/maker/checker can view only the pending changes)

  • the section/entity the change affects (so the particular part of the FE can show a pending change and disallow further pending changes)

The change record needs to contain enough data to show in a human-readable way

  • What was changed [entity + action (including attribute name and attribute value if applicable)]

  • For whom [customer ID + name]

  • Who did it [maker ID + name]

  • When [timestamp]

  • Why [ticket ID, is available]

Main flows

Diagram notes

API

https://backoffice-manager.apps.brave.safibank.online/swagger/views/swagger-ui/#/Change

Making

Names are self-explanatory, see the models for what exact changes can be done by them.

POST /changes/accounts
POST /changes/cards
POST /changes/customers
POST /changes/documents
POST /changes/idCards
POST /changes/products                // make a change on subscription
POST /changes/transactions

Checking

POST /changes/{changeId}/approve      // approves a pending change
POST /changes/{changeId}/reject       // rejects a pending change

Viewing

GET  /changes/{changeId}              // get single change by ID
GET  /changes                         // list all changes (in all states), not paged

Changes can be filtered by customerId, entityType and a list of statuses.

The filter by ticket ID, maker or checker could be implemented (BE has the data) but is not and that filtering is done by FE.

No paging or sorting is supported in BE, all done on the FE side.

Examples of listing changes:

  • Get all pending changes for this customer

    GET /changes?customerId={id}&status=[PENDING]
  • Get all changes for a particular maker
    No BE query, filter by makerId on FE

  • Get all changes for a particular checker
    No BE query filter by allowedCheckerIds o FE (show only changes there that array includes the ID particular user)

Data model

// "Make account change" input payload example
AccountChangeCreateDto: {   // DTO for specific domain (account)
  "base": {
    "ticketId": "BOSD-506",
    "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "entityId": "8e8284d6-04e6-48f5-bd13-3030f9c7a83d",
    "entityName": "Account name"
  },
  // renaming main account
  "updatedAttributes": {
    "name": "New name"
  },
}
// Data passed to Account domain once the change is approved
AccountUpdateDto: {
  name: “new name“,
  pictureLink: null,
  savingAccount: null
}
// Entity saved for querying changes
ChangeWorkflowEntity: {
  "id": “24521ff5-d43f-41fe-a325-efe839f37625”,,
  "entityType": "ACCOUNT",
  "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "ticketId": "BOSD-506",
  "makerId": "00u1ye02mtvGiZOtA697", // resolved automatically by BE
  "allowedCheckerIds": [ "00u1ye02mtvGiZOtA697" ], // resolved automatically by BE
  "status": "PENDING"
}
// Data returned to BOFE
ChangeResponseDto: {     // DTO of all changes (all domains)
  "id": “00000000-0000-4aaa-aaaa-aaaaaaaaaaaa”,
  "base": {
    "ticketId": "BOSD-506",
    "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "entityId": "8e8284d6-04e6-48f5-bd13-3030f9c7a83d",
    "entityName": "Account name"
  },
  "makerId": "00u1ye02mtvGiZOtA697",       // who created the change
  "allowedCheckerIds": [ "00u1ye02mtvGiZOtA697" ], // who can approve/reject
  "checkerId": "00u1ye02mtvGiZOtA697"      // who actually did approved/rejected
  "status": "REJECTED",
  "accountUpdatedAttributes": {
    "name": "New name"
  }
}

Implementation

There are 5 change statuses.

The change lifecycle is implemented using Temporal.io workflows (see Temporal)

Each workflow has a series of activities that happen once a certain trigger happens.

The universal ChangeWorkflow looks like this, each activity has a separate colour.

When implementing a new change, only the green “apply change” activity needs to be implemented, the rest are universal.

Automatic changes cleanup

If a change is still pending at the end of the day it is supposed to be automatically rejected.

This is implemented via RejectPendingChangesJob class. The “end of the day” is calculated in the Asia/Manila timezone.

Important: The Temporal.io is removing all it’s workflows after 3 days.

Given that all changes are invalidated at the end of the day anyways it was kept as is.

The main consequence is that details about changes are not available for FE other than that time period. However all information related to the change is being audit logged so that info is available in the audit log.

Kafka messages

This component publishes snapshots of Change entity for Datalake. See https://safibank.atlassian.net/wiki/spaces/ITArch/pages/219973494/Kafka+messages#Producers.

It’s also doing standard audit logging. See Audit log manager.

Attachments:

plantuml_1655828870082 (text/plain)
plantuml_1655828870082.svg (image/svg+xml)
plantuml_1655828870082.png (image/png)
plantuml_1655830290624.svg (image/svg+xml)
plantuml_1655830290624 (text/plain)
plantuml_1655830290624.png (image/png)
plantuml_1655828870082.svg (image/svg+xml)
plantuml_1655828870082 (text/plain)
plantuml_1655828870082.png (image/png)
plantuml_1655830290624 (text/plain)
plantuml_1655830290624.svg (image/svg+xml)
plantuml_1655830290624.png (image/png)
plantuml_1655828870082.svg (image/svg+xml)
plantuml_1655828870082 (text/plain)
plantuml_1655828870082.png (image/png)
plantuml_1655828870082.svg (image/svg+xml)
plantuml_1655828870082 (text/plain)
plantuml_1655828870082.png (image/png)
plantuml_1655828870082 (text/plain)
plantuml_1655828870082.svg (image/svg+xml)
plantuml_1655828870082.png (image/png)
plantuml_1655828870082.svg (image/svg+xml)
plantuml_1655828870082 (text/plain)
plantuml_1655828870082.png (image/png)
plantuml_1655828870082.svg (image/svg+xml)
plantuml_1655828870082 (text/plain)
plantuml_1655828870082.png (image/png)
plantuml_1655983413383 (text/plain)
plantuml_1655983413383.svg (image/svg+xml)
plantuml_1655983413383.png (image/png)
plantuml_1655828870082.svg (image/svg+xml)
plantuml_1655828870082 (text/plain)
plantuml_1655828870082.png (image/png)
plantuml_1655828870082.svg (image/svg+xml)
plantuml_1655828870082 (text/plain)
plantuml_1655828870082.png (image/png)
plantuml_1655983413383.svg (image/svg+xml)
plantuml_1655983413383 (text/plain)
plantuml_1655983413383.png (image/png)
plantuml_1655828870082 (text/plain)
plantuml_1655828870082.svg (image/svg+xml)
plantuml_1655828870082.png (image/png)
plantuml_1655983413383.svg (image/svg+xml)
plantuml_1655983413383 (text/plain)
plantuml_1655983413383.png (image/png)
plantuml_1655983413383.svg (image/svg+xml)
plantuml_1655983413383 (text/plain)
plantuml_1655983413383.png (image/png)
plantuml_1657702396260.svg (image/svg+xml)
plantuml_1657702396260 (text/plain)
plantuml_1657702396260.png (image/png)
plantuml_1655983413383.svg (image/svg+xml)
plantuml_1655983413383 (text/plain)
plantuml_1655983413383.png (image/png)
plantuml_1657702566056.svg (image/svg+xml)
plantuml_1657702566056 (text/plain)
plantuml_1657702566056.png (image/png)
plantuml_1655983413383 (text/plain)
plantuml_1655983413383.svg (image/svg+xml)
plantuml_1655983413383.png (image/png)
plantuml_1655983413383.svg (image/svg+xml)
plantuml_1655983413383 (text/plain)
plantuml_1655983413383.png (image/png)
plantuml_1657702642249 (text/plain)
plantuml_1657702642249.svg (image/svg+xml)
plantuml_1657702642249.png (image/png)
plantuml_1657702808512.svg (image/svg+xml)
plantuml_1657702808512 (text/plain)
plantuml_1657702808512.png (image/png)
plantuml_1657702566056.svg (image/svg+xml)
plantuml_1657702566056 (text/plain)
plantuml_1657702566056.png (image/png)
plantuml_1657702566056 (text/plain)
plantuml_1657702566056.svg (image/svg+xml)
plantuml_1657702566056.png (image/png)
plantuml_1657702642249.svg (image/svg+xml)
plantuml_1657702642249 (text/plain)
plantuml_1657702642249.png (image/png)
plantuml_1657702642249 (text/plain)
plantuml_1657702642249.svg (image/svg+xml)
plantuml_1657702642249.png (image/png)
plantuml_1657702808512.svg (image/svg+xml)
plantuml_1657702808512 (text/plain)
plantuml_1657702808512.png (image/png)
plantuml_1657710876036.svg (image/svg+xml)
plantuml_1657710876036 (text/plain)
plantuml_1657710876036.png (image/png)
plantuml_1655828870082.svg (image/svg+xml)
plantuml_1655828870082 (text/plain)
plantuml_1655828870082.png (image/png)
plantuml_1655830290624 (text/plain)
plantuml_1655830290624.svg (image/svg+xml)
plantuml_1655830290624.png (image/png)
plantuml_1669287545070.svg (image/svg+xml)
plantuml_1669287545070 (text/plain)
plantuml_1669287545070.png (image/png)
plantuml_1655830290624 (text/plain)
plantuml_1655830290624.svg (image/svg+xml)
plantuml_1655830290624.png (image/png)
plantuml_1655828870082.svg (image/svg+xml)
plantuml_1655828870082 (text/plain)
plantuml_1655828870082.png (image/png)
plantuml_1669287545070 (text/plain)
plantuml_1669287545070.svg (image/svg+xml)
plantuml_1669287545070.png (image/png)
plantuml_1669287545070 (text/plain)
plantuml_1669287545070.svg (image/svg+xml)
plantuml_1669287545070.png (image/png)