Skip to content
This repository was archived by the owner on Aug 18, 2021. It is now read-only.

Business Error Handling

Gustavo Denis edited this page Oct 31, 2019 · 1 revision
Main > Key Concepts > Business Error Handling

There are many strategies for dealing with errors, being them expected/conditional or unexpected/abnormal.

The later ones are better dealt with exceptions. For that Liquid handles invalid inputs (bad requests) and invalid models (inconsistent data) using FluentValidation strategies and managing exceptions and error codes transparently for the developer.

On the other hand, application (business) logic usually deals with expected/conditional ones. These are errors issued by back-ends (or Object Models) and they have business contextual meaning, such as for example: "No sufficient balance to complete the transaction" or "There are not enough items in stock to fulfill your request".

Actually, this kind of error is supposed to never be induced by presentation logic, that is, the user experience should be designed in such a way as to avoid such path of domain (business) logic.

Therefore, in the above examples, the presentation logic would better only allow transactions that met user's funds. And products should only be enabled to be ordered in quantities their stocks were sufficient for.

But not always that is the case: such conditions are inevitably produced in back-ends. So it is mandatory having those back-ends as true gatekeepers blocking transactions due to expected (yet undesirable) fatal conditions: the so called business errors.

Additionally, back-ends usually check near critical conditions (warnings) as well as produce transactional contextual information, handing them over to front-ends for standard presentation to users.

For instance, after adding a new item to a basket, the back-end could raise a warning such as "There are only 3 items of this product left in stock. Please, consider submitting your order as soon as possible".

And after processing an order, the back-end could send an information message such as "Your order has been accepted. Please wait for an e-mail message with instructions on how to track your delivery".

Liquid prescribes a standard way of doing this type of in-transaction business critics. See the following back-end sample code (with unstructured returns so as to improve readability):

public class BasketService : LightDomain
{
    public DomainResponse AddItem(string basketId, ItemVM itemVM)
    {
         DomainResponse res = null;

         var basket = await Repository.GetByIdAsync<BasketModel>(basketId);
         if (basket == null)
         {
             AddBusinessError("BASKET_NOT_FOUND");
             return Response(res);
         }

         await new LightApi("STOCK").PostAsync($"{itemVM.ProductId}/reservefor/{basketId}?quantity={itemVM.Quantity}");

         //Errors from other Liquid microservices are automatically inserted into the calling one
         if (this.HasBusinessErrors)
         {
             //Business errors (and others) from Stock API are sent back to original caller as if they were Basket's 
             return Response(res);
         }
         else
         {
             ItemModel item = new ItemModel();
             item.MapFrom(itemVM);

             basket.Items.Add(item);
             res = await Repository.AddOrUpdateAsync(basket);
            
             return Response(res);
        }
    }
}

...

public class StockService : LightDomain
{
    public DomainResponse ReserveForBasket(string productId, string basketId, int quantity)
    {
       ...

       if (stock.Quantity < quantity)
          {
             AddBusinessError("NOT_ENOUGH_ITEMS_ON_STOCK");
             return Response(res);
          }
          else
          {
             if (stock.Quantity - quantity <= 3)
             {
                AddBusinessWarning("STOCK_CRITICALLY_LOW");
             }

             ...


public class OrderService : LightDomain
{
    public DomainResponse SubmitOrder(string orderId)
    {
        ...

        AddBusinessInfo("DELIVERY_TRACKING_FINAL_INSTRUCTIONS");
        return Response(res);
    }
}

Additionally, front-ends written on top of Liquid also benefit from the same approach.

See the following Angular front-end sample code automatically highlighting the product with low stock:

<section class="flex-container">
  <critic-handler #criticname='critichandler'>
     <form class="basket-form" #myForm="ngForm" (ngSubmit)="onSubmit()">
        <input matInput placeholder="Product" name="name" id="productName" #itemName [(ngModel)]="name" #nameInput="ngModel"
            business-field [trackCritics]="'STOCK_CRITICALLY_LOW'"
            (register)='criticname.register($event)'>

            ...

The same can be done by a React front-end using Liquid primitives:

render() {
    return <CriticHandler messageMain="CRITICS ON THE FORM">
             <LightForm name="basket">
                <label> Product: </label>
                <LightInput type="text" name="name" onChange={this.onChangeHandle}  value={this.state.productName} 
                codebusinesscritics="STOCK_CRITICALLY_LOW'" showdetails="true" businessowner={this.businessowner} />

              ...
           </CriticHandler>  
  }

In conclusion, this is another way in which Liquid abstracts technical complexity, freeing developers to focus on coding domain (business) logic in a straightforward yet highly flexible and extensible manner.

Clone this wiki locally