It's my first time sharing my experience.
Let me know if there's anything I can improve.
Introduction
"Consumer-Driven Development" is a made-up terminology.
In this mindset, we define what the "consumer" needs first, then orchestrate the application to satisfy the need.
The "consumer" here can be perceived as the presentation layer, such as API controllers.
Example
The existing architecture
We have an application.
It calls an API endpoint periodically and logs the data:
The diagram is drawn using PlantUML.
PlantUML Text
@startuml Example App
skinparam componentStyle rectangle
cloud "API Endpoint"
cloud "Elastic"
rectangle "Application" {
[Gateway]
[API Client]
[Repository]
[Cron Job]
}
[API Endpoint] -> [Gateway]
[Gateway] --> [API Client] : HTTP Response
[API Client] --> [Repository] : Product DTO
[Repository] --> [Cron Job] : Product
[Cron Job] -> [Elastic] : Logs
@enduml
The new business requirement
The API endpoint provides a field called status
.
It can be Approved
, Disapproved
, Pending
.
We want to log the total number of Approved
products.
Solution #1 - The data-flow approach
PlantUML Text
@startuml
skinparam componentStyle rectangle
skinparam defaultTextAlignment center
component [Repository] as "**Step 3**\nRepository"
component [CronJob] as "**Step 4**\nCronJob"
[API Client] -> [Repository] : **Step 1**\nProductDTO
[Repository] -> [CronJob] : **Step 2**\nProduct
@enduml
Step 1:
We want to obtain the data from the API endpoint.
Therefore, we create ProductDTO.Status
as a text field.
Step 2:
We want meaningful data in our domain object.
Therefore, we create Product.Status
as an enum.
enum Status
{
Approved,
Disapproved,
Pending,
}
Step 3:
Let Repository
do the translation.
(Note: Exception handling is omitted.)
var products = productDtos.Select(dto =>
{
return new Product() { Status = Enum.Parse<Status>(dto.Status) };
});
Step 4:
Finally, we can log the number of approved products in CronJob
.
logger.Information(
"Number of approved products: {ApprovedProductCount}",
products.Count(product => product.Status == Status.Approved));
Solution #2 - The consumer-driven approach
PlantUML Text
@startuml
skinparam componentStyle rectangle
skinparam defaultTextAlignment center
component [Repository] as "**Step 2**\nRepository"
component [CronJob] as "**Step 1**\nCronJob"
[API Client] -> [Repository] : **Step 2**\nProductDTO
[Repository] -> [CronJob] : **Step 1**\nProduct
@enduml
Step 1:
The consumer, CronJob
, needs to know whether a product is approved.
Therefore, we create Product.IsApproved
as a boolean field.
logger.Information(
"Number of approved products: {ApprovedProductCount}",
products.Count(product => product.IsApproved));
Step 2:
The provider, Repository
, will figure out how to populate the new IsApproved
field.
To do so, we create ProductDTO.Status
as a text field.
var products = productDtos.Select(dto =>
{
return new Product() { IsApproved = dto.Status == "Approved" };
});
Result
In solution #1, the enum value Status.Disapproved
and Status.Pending
are unused. We spent extra effort to handle exceptions when translating the text field into enum.
In solution #2, the use of boolean field makes the code much simpler. One could argue that it lacks data validation and scalability, but that is not within the requirement for today.
Conclusion
Using Consumer-Driven Development, we ensure that our effort is spent to meet the requirement, and nothing else.
Happy to hear your thoughts. 😉
Top comments (0)