CORE CONCEPTS

 

A cloud-native application with our technology consists of three major layers: business logic, D-ASYNC middle layer, and a cloud platform.

The development of an app or one of its parts starts with defining a domain boundary. The surface of the functional responsibilities defines the contract of a service. Then the core business logic can be coded first without addressing most of the non-functional requirements. A service can consume another service by referencing its contract without any extra code.

D-ASYNC currently works with C# and .NET

The D-ASYNC layer translates abstractions of a programming language into service-oriented patterns and delegates the execution to a platform. Unlike using a framework, D-ASYNC references the code of the business logic and not the other way around.

The cloud platform can be either public or private and can be customized later independently from the core functionality of your application. The customization options provided by D-ASYNC platform can be switched on-the-fly with a single operation.

 

Inter-Service Communication

Any service contract is defined by an interface, which can be used by another service. A service is a class with a set of methods.

 

There are no API controllers, service clients, request and response DTOs (data transfer objects), or extra code for async APIs.

Unlike RPC (Remote Procedure Call), calling another service is reliable by default without a need for infrastructure error handlers (like timeouts).

Service Injected

as Dependency

public interface IUserService

{

  Task EnableUser(string id);

}


public class RegistrationService : IRegistrationService
{
 
private readonly IUserService userService;

   

  public RegistrationService(IUserService userService)
  {
   
this.userService = userService;
  }

 

  public async Task ConfirmRegistration(string userId)
  {
   
await userService.EnableUser(userId);
  }
}

External Service Definition

Local Service Definition

Reliable Service Call

 

Event-Driven Design

Events are building blocks of reactive programming that help to decouple logical components of an application. A service contract can define events that other services can subscribe to.

There is no framework-specific syntax to publish or listen to events. There is no need to create a publisher-subscriber manager.

Unlike using typical event/message hub clients, the event publishing within a method is guaranteed by default without any complex code that guarantees consistency.

Instead of choosing between orchestration or choreography, mix and match both to place the business logic where it belongs to.

Event Definition

Reliable Event Handler

public class UserService : IUserService
{
 
public virtual event EventHandler<UserInfo> UserRegistered;

 

  public virtual async Task RegisterUser(string userName)
  {
   
var userInfo = new UserInfo { /*...*/ };
   
// ...
    UserRegistered?.Invoke(userInfo);
  }
}

public class NewsletterService : INewsletterService
{
 
public NewsletterService(IUserService userService)
  {
    userService.UserRegistered += OnUserRegistered;
  }

  protected virtual Task OnUserRegistered(UserInfo userInfo)
  {
   
// ...
  }
}

Publish Event

Subscribe to Event

 

Persisted Workflows

Workflows consist of a series of steps - routines and sub-routines - a set of hierarchical state machines. A method call, either external or local, can be seen as a step in a workflow. The code between those calls represents a transition of a state machine. The input arguments and local variables of a method are the persisted state.

There is no need to draw a diagram of a workflow, need to create a message, event, state DTOs (Data Transfer Objects), need to create handlers. Less time spent on understanding the flow.

Similarily to the async-await feature versus basic tasks and their continuations, a language-native compact form achieves the same result.

Instead of modeling workflows and focusing on their reliable execution with a framework, write business logic as a sequence of methods. In the case of infrastructure failure, the workflow will auto-resume the step without starting from the beginning.

All service methods and event handlers are routines by default, and there is no distinguishing between services that operate in a synchronous request-response manner and services that perform long-running asynchronous operations.

public class OrderPlacementService
{


  public async Task PlaceOrder(string itemId, int quantity)
  {
   
var purchaseId = Guid.NewGuid();
   
var price = CalculatePrice(itemId, quantity);

    await paymentService.Credit(purchaseId, price);
   

    try
    {
     
await warehouseService.ReserveItem(
            purchaseId, itemId, quantity);
    }
   
catch (OutOfStockException)
    {
     
await paymentService.Debit(purchaseId, price);
    }

    await FinalizePurchase();
  }

  protected virtual async Task FinalizePurchase()
  {
   
// ...
  }
}

Routine

External Service Routines

Compensating Action

Sub-Routine

Step 1

Step 2

Step 2-F

Step 3

Local Service Step

 

Transaction Guarantee

Reliable and idempotent method execution is a hard thing to guarantee, especially when it involves modifications of entities in a SQL-like database. When an infrastructure failure occurs, usually something does not happen or happens twice leaving the application in an inconsistent state.

When combined with a database like SQL Server, by default there is no need to write complex code that may guarantee exactly-once semantics. There is no need to implement a concurrency control to make sure that only one operation on an entity performs at a time.

The Unit-of-Work pattern is implemented implicitly within the scope of a method or a step of a workflow, what binds together communication primitives and persisted entities.

public class UserService
{
 
private readonly IUserRepository userRepository;

  public async Task EnableUser(string userId)
  {
   
User user = await userRepository.GetUserById(userId);
   
if (!user.IsEnabled)
    {
      user.IsEnabled =
true;
      UserEnabled?.Invoke(user);
    }
  }

  public virtual event EventHandler<User> UserEnabled;

}

DB-backed Abstraction

Optimistic Concurrency Control

Invocation Guarantee

Transaction Scope

 

CloudSharp

Having paradigms of a general-purpose programming language is not enough for cloud-native development. The language itself should evolve to address the modern needs of software developers.

The CloudSharp project will extend C# language with new syntax constructs to express the service-oriented concepts (as shown above) in a more meaningful human-readable way.

The project will also bring a set of tools to seamlessly guide through the development experience by suggesting how the code should look like and warning about unintentional harmful actions to save from a disaster in a production environment.

CloudSharp-banner.png
 

WHERE IS INFRASTRUCTURE?

DELAYED DESIGN

 

Non-functional requirements come second. The business logic remains exactly the same regardless if it is hosted on Windows or Linux, if it uses containers and a container orchestrator, if it runs on a serverless platform or a typical VM, if methods are invoked via HTTP or a message-passing mechanism, if API calls are asynchronous or not.

You may delay design decisions and swap the underlying hosting and communication infrastructure later on without changing the code of your application.

OPTIMIZED TRIGGERS

The same method of a service can have multiple triggers like HTTP and message bus. One can be used from the front-end with more relaxed consistency guarantees, another one can be used from the back-end in a more strict mode.

All methods of a single service do not have to use exactly the same communication mechanism. You may configure one to be synchronous HTTP call and another one use message bus only depending on how you want to balance scalability, reliability, and latency.

DEPLOYMENT SPLIT

In some cases, we tend to create multiple services due to the differences in hardware. For example, a managing service receives an HTTP request and puts a message on a queue, which is passed to a long-running processing service.

Instead, those two services logically can be a single one. A single method or a group of methods (workflow) can be configured to run on a different deployment, or even spin up a container for isolated execution. In this case, the unit of deployment can be as small as one method.

Twitter-White.png
LinkedIn-White.png

© 2020

with passion ❤ D-ASYNC

Early Experience