DEV Community

Samuel Ko
Samuel Ko

Posted on

An example of Consumer-Driven Development

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:

Component Diagram

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
Enter fullscreen mode Exit fullscreen mode

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

Conceptual diagram

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
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

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) };
});
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

Solution #2 - The consumer-driven approach

Conceptual diagram

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
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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" };
});
Enter fullscreen mode Exit fullscreen mode

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)