Introduction
So, here I'm again. Writing after some time without doing anything like it, but I've decided to share this, because I think this solution saved a lot of work and problems that system could had. The context was this: my team was responsible for an application that was been integrated into a SAP system by another company, and, we had several PSP integrations to provide our services.
Some of this PSP test enviroments didn't work at all, they were flaky and inconsistant with responses and even the types of the response properties between the test and production environment. We needed to provide a way for the SAP consultancy to be able to test the functionalities of our plataform without the instability of the PSP test enviroment, and then, we decided to add a flag to mock the PSP response to be successfull (since it was the outcome that we were more interested in).
It was al great, untill we realized that we needed to test the refund functionality. Now what? As our application was built upon microsservices with domain segregation. We had an Anti-Corruption-Layer (ACL) between it and the PSP, so we had no idea of what was happening there. Both payment and refund were in the ACL, but it hadn't any persistance, as was built to be a facade between the third-party service provider and our domain. How could we determine if a payment was done in the actual service provider test environment or was mocked by our ACL?
Aproaches
- Add a parameter in the response of the ACL indicating if this response is mocked or not;
- Add a database in the ACL to save all the payments created, and if they were mocked or not.
Aditional Parameter
If we had chosen this path, it would require that the application consuming the ACL was changed, to consider this new information about mocking, then, this consuming application would need to save this in order to provide this information back to the ACL when refund were requested. It required to much development just to enable this, and it didn't add anything to our product, just it's testability experience.
Database in the ACL
Imagine this, a service that operated without databases in production would require one to work in test enviroment. I promptly refused, even not enjoying the first option, this was way worst. Not only would require aditional infrastructure, but also add futher complexity and latency to the system.
Avoinding these pains
So, I've started to think. How could I know this information without adding any furter infrastructure or adding parameters? Somehow, I needed to be able to split this register with the information that they already had. With that in mind, I've started to come up with a solution.
UUID - The Trojan Horse
Every transation would return an UUID that should be generated by the PSP (or not). Somehow mocking this information in a way that it would be differentiable from the PSP generated ID's wouldn't affect the transaction information, all the further information required would be maintained. With that in mind, I wen't on several experiments in order to make this possible, and that's how I've Achieved it:
Marked UUID
UUIDs are pseudo-random 128-bit identifiers that are often represented as 32 hexadecimal digits, displayed in five groups separated by hyphens, in the form 8-4-4-4-12 (e.g., 123e4567-e89b-12d3-a456-426614174000).
UUIDv4
UUIDv4 is one of the most commonly used versions of UUIDs. It is designed to be a universally unique identifier generated using random or pseudo-random numbers. Here's a detailed overview of UUIDv4:
Characteristics of UUIDv4
Randomness: UUIDv4 relies on random or pseudo-random numbers to generate the unique identifier.
No External Information: Unlike other versions of UUIDs, such as UUIDv1 (which incorporates timestamps and MAC addresses), UUIDv4 does not include any external information about the generating system.
Simplicity: UUIDv4 is straightforward to implement because it only requires a good source of randomness.
Structure of UUIDv4
A UUIDv4 is a 128-bit value, typically represented as a 36-character string in the format 8-4-4-4-12, separated by hyphens.
Example UUIDv4
f47ac10b-58cc-4372-a567-0e02b2c3d479
Breakdown of UUIDv4
- 8 characters: Randomly generated 32 bits.
- 4 characters: Randomly generated 16 bits.
- 4 characters: Randomly generated 12 bits, and 4 bits for the version (0100 for version 4).
- 4 characters: Two or three bits for the variant (10x for RFC 4122), and - 13 or 14 randomly generated bits.
- 12 characters: Randomly generated 48 bits.
Marking UUIDv4
The process to mark this ID should be something that couldn't be done by an valid UUID generation, but It should be compatible with UUID implementations in order to be stored sucesfully, avoiding any changes outter the Anti-Corruption Layer code.
I've selected the last 12 characters section to mark the ID, but I needed for it to be unmistaken with real IDs, so I've devised this algorithm:
- Take the ID without the last 12 characters and the hypens
- Use a hash algorithm like SHA-256 to creat a mark
- Grab the first 12 characters (48 bits) of the hash and replace the original generated 12 characters
That's it. Stupidly simple, works with the language implementation (in my case was golang) and was unmistaken with a real gerenated UUID. But how can we determine if an ID is marked? It is as simple as to create one:
- Take the ID without the last 12 characters and the hypens
- Use a hash algorithm like SHA-256 to creat a mark
- Grab the first 12 characters (48 bits) of the hash and compare it to the IDs last 12, if they're equal, this ID was marked.
Solution
So, how did I've employed this to solve the problem? Simple, when I had the flag to mock the PSP responses enabled, I've returned at the payment endpoint the mocked response with an marked ID. In the refund endpoint, if the flag was enabled, I checked if the ID was marked, if so, I would return a mocked refund response. If not, I would make the PSP call, as this payment was created in the PSP enviroment.
Conclusion
If something you gotta do seems to require too much corromption to your application, look for alternative approaches to this problem and don't be afraid to try new things out!
Any sugestions, comments or corrections, fell free to reach me out at LinkedIn.
Top comments (0)