One of the primary goals of our BFF is to reduce the frontend’s complexity. When examining the Baskets service, we realized it would add a bit of avoidable complexity if we were only to serve the raw operation, so instead, we decided to encapsulate all of the shopping cart logic behind a single endpoint. When a client POST to the api/cart endpoint, it:
- Adds a non-existent item.
- Update an existing item’s quantity.
- Remove an item that has a quantity equal to 0 or less.
With this endpoint, the clients don’t have to worry about adding or updating. Here’s a simplified sequence diagram that represents this logic:

Figure 19.29: A sequence diagram that displays the high-level algorithm of the cart endpoint.
As the diagram depicts, we call the remove endpoint if the quantity is inferior or equal to zero. Otherwise, we try to add the item to the basket. If the endpoint returns a 409 Conflict, we try to update the quantity. Here’s the code:
app.MapPost(
“api/cart”,
async (UpdateCartItem item, IWebClient client, ICurrentCustomerService currentCustomer, CancellationToken cancellationToken) =>
{
if (item.Quantity <= 0)
{
await RemoveItemFromCart(
item,
client,
currentCustomer,
cancellationToken
);
}
else
{
await AddOrUpdateItem(
item,
client,
currentCustomer,
cancellationToken
);
}
return Results.Ok();
}
);
The preceding code follows the same pattern but contains the previously explained logic. We explore the two highlighted methods next, starting with the RemoveItemFromCart method:
static async Task RemoveItemFromCart(UpdateCartItem item, IWebClient client, ICurrentCustomerService currentCustomer, CancellationToken cancellationToken)
{
try
{
var result = await client.Baskets.RemoveProductFromCart(
new Web.Features.Baskets.RemoveItem.Command(
currentCustomer.Id,
item.ProductId
),
cancellationToken
);
}
catch (ValidationApiException ex)
{
if (ex.StatusCode != HttpStatusCode.NotFound)
{
throw;
}
}
}
The highlighted code of the preceding block leverages the typed HTTP client and sends a remove item command to the Baskets service. If the item is not in the cart, the code ignores the error and continues. Why? Because it does not affect the business logic or the end-user experience. Maybe the customer clicked the remove or update button twice. However, the code propagates to the client any other error.Let’s explore the AddOrUpdateItem method’s code:
static async Task AddOrUpdateItem(UpdateCartItem item, IWebClient client, ICurrentCustomerService currentCustomer, CancellationToken cancellationToken)
{
try
{
// Add the product to the cart
var result = await client.Baskets.AddProductToCart(
new Web.Features.Baskets.AddItem.Command(
currentCustomer.Id,
item.ProductId,
item.Quantity
),
cancellationToken
);
}
catch (ValidationApiException ex)
{
if (ex.StatusCode != HttpStatusCode.Conflict)
{
throw;
}
// Update the cart
var result = await client.Baskets.UpdateProductQuantity(
new Web.Features.Baskets.UpdateQuantity.Command(
currentCustomer.Id,
item.ProductId,
item.Quantity
),
cancellationToken
);
}
}
The preceding logic is very similar to the other method. It starts by adding the item to the cart. If it receives a 409 Conflict, it tries to update its quantity. Otherwise, it lets the exception bubble up the stack to let an exception middleware catch it later to uniformize the error messages.With that code in place, we can send POST requests to the api/cart endpoint for adding, updating, and removing an item from the cart. The three operations return an empty 200 OK response.Assuming we have an empty shopping cart, the following request adds 10 Habanero Peppers (id=3) to the shopping cart:
POST https://localhost:7254/api/cart
Content-Type: application/json
{
“productId”: 3,
“quantity”: 10
}
The following request adds 5 Apples (id=2) to the cart:
POST https://localhost:7254/api/cart
Content-Type: application/json
{
“productId”: 2,
“quantity”: 5
}
The following request updates the quantity to 20 Habanero Peppers (id=3) :
POST https://localhost:7254/api/cart
Content-Type: application/json
{
“productId”: 3,
“quantity”: 20
}
The following request removes the Apples (id=2) from the cart:
POST https://localhost:7254/api/cart
Content-Type: application/json
{
“productId”: 2,
“quantity”: 0
}
Leaving us with 20 Habanero Peppers in our shopping cart (GET https://localhost:7254/api/cart):
[
{
“id”: 3,
“name”: “Habanero Pepper”,
“unitPrice”: 0.99,
“quantity”: 20,
“totalPrice”: 19.80
}
]
The requests of the previous sequence are all in the same format, reaching the same endpoint but doing different things, which makes it very easy for the frontend client to manage.
If you prefer having the UI to manage the operations individually or want to implement a batch update feature, you can; this is only an example of what you can leverage a BFF for.
We are now done with the BFF service.