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
All users can see all pending changes when viewing that entity
Makers can view the full list of changes they made recently
Checkers can view the full list of changes they are allowed to approve
For details about notifications see https://safibank.atlassian.net/wiki/spaces/ITArch/pages/190776618/Backoffice+notifications#BOFE-web-notifications
For details about logging see Audit log manager
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 status
es.
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 bymakerId
on FEGet all changes for a particular checker
No BE query filter byallowedCheckerIds
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.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)