Sign up for the early invite to the fully managed experience with D-ASYNC Platform.
D-ASYNC is a multifaceted and comprehensive solution for building service-oriented applications. It is based on the new Service-Oriented Programming Language paradigms, which provide an extendible framework for inter-service communication (HTTP, gRPC, message queues, event streams), a unique language-integrated stateful workflow engine, implementation of best microservice practices, a unified approach for distributed tracing, API generator and versioning capabilities, error-handler free clean code.
The mission of D-ASYNC is to give developers a superpower of zero-cost development of scalable, reliable, and secure microservices. The ability to use the language itself helps to focus on the core value of your application, making it easy to write, read, evolve, and maintain.
The following examples may look trivial and as for a single-process application, but D-ASYNC technology makes them run (without any modifications) in a resilient, persistent, scalable, and distributed manner. The new service-oriented syntax will be introduced with the CloudSharp project.
- Inter-Service Communication.
// Declaration of the interface of another service
// that might be deployed in a different environment.
public interface IPaymentTerminal
{
Task Pay(Order order, CreditCard card);
}
public class BaristaWorker
{
private IPaymentTerminal _paymentTerminal;
// Another service can be used with the dependency
// injection. All calls to that service will use
// its communication mechanism. All replies will
// be routed back to this service.
public BaristaWorker(IPaymentTerminal paymentTerminal)
{
_paymentTerminal = paymentTerminal;
}
protected virtual async Task<Order> TakeOrder()
{
Order order = ...;
CreditCard card = ...;
// Simple call to another service may ensure
// consistency between two. That complexity is
// hidden to help you focus on the business logic.
await _paymentTerminal.Pay(order, card);
// Unlike any RPC, the state of execution is saved,
// and restored when the service replies. If the call
// fails, it is automatically retried and an exception
// is never seen in this method.
// There is no need to create API controllers and service
// clients, and no need to worry about asynchronous APIs.
}
}
- Service Events and Reactive Programming.
public class CustomerManagementService : ICustomerManagementService
{
// Implement the domain event simply as a C# event.
public virtual event EventHandler<CustomerInfo> CustomerRegistered;
public virtual async Task RegisterCustomer(CustomerInfo customerInfo)
{
// ... (register a new customer in the system here)
// Then notify observers about successful registration.
// The event may not fire immediately, but will get scheduled
// when this method exits to guarantee consistency.
CustomerRegistered?.Invoke(this, customerInfo);
}
}
public class RewardProgramService : IRewardProgramService
{
public RewardProgramService(
ICustomerManagementService customerService)
{
// Subscribe to the domain event.
customerService.CustomerRegistered += OnCustomerRegistered;
}
protected virtual void OnCustomerRegistered(
object sender, CustomerInfo customerInfo)
{
// This method is executed in a resilient manner
// and can be a workflow (see next example).
}
}
- Stateful Workflows.
// This is a service with a workflow.
public class BaristaWorker
{
// This is a 'routine' of a workflow.
public virtual async Task PerformDuties()
{
// This will call a sub-routine and save the state
// of the current one.
var order = await TakeOrder();
// If the process terminates abruptly here, after restart
// the routine continue at exact point without executing
// previous steps. Any async method is compiled into a
// state machine, so it's possible to save and restore
// its state and context.
var cup = await MakeCoffee(order);
// Essentially this is an Actor Model of a scalable
// distributed system. A routine maps to an actor,
// because an async method compiles into a state
// machine (which has its state), and a routine can
// call sub-routines - same as an actor can invoke
// other actors.
await Serve(cup);
// Workflows are not limited to one service and
// its methods, and can span across many services
// (as shown in the very first example and below).
// Varies degrees of consistency can be guaranteed
// in case of failures, and that settings does not
// change the business logic.
}
// This is a 'sub-routine' of a workflow.
protected virtual async Task<Order> TakeOrder();
protected virtual async Task<Cup> MakeCoffee(Order order);
protected virtual async Task Serve(Cup cup);
}
- Saga Pattern.
// This method represents a workflow with
// application-specific error-handling.
public async Task PlaceOrder()
{
// Hard-coded sample input.
var price = 10;
var itemId = "Whole Coffee Beans 1lb";
var quantity = 1;
// Generate unique ID which will be persisted in this routine.
var transationId = Guid.NewGuid();
// 1. First, make sure that payment can be made.
// This is a call to a service #1.
await _paymentProcessor.Credit(transationId, price);
try
{
// 2. Then, reserve the item being purchased.
// This is a call to a service #2.
await _warehouse.ReserveItem(transationId, itemId, quantity);
}
catch (OutOfStockException)
{
// 3. If they are out of stock, refund the cost of an item.
// Perform a compensating action on service #1.
await _paymentProcessor.Debit(transationId, price);
}
// This method acts as an orchestrator and represents clear
// business logic of placing an order without a need to
// decompose it into dozens of message classes and their
// respective handlers.
}
- Parallel execution.
public class CoffeeMachine
{
public virtual async Task PourCoffeeAndMilk(Cup cup)
{
// You can execute multiple methods in parallel
// to 'horizontally scale out' the application.
Task coffeeTask = PourCoffee(cup);
Task milkTask = PourMilk(cup);
// Then just await all of them, as you would
// normally do with TPL.
await Task.WhenAll(coffeeTask, milkTask);
// This is translated into a such series of steps:
// 1. Save state of current method;
// 2. Schedule PourCoffee
// 3. Schedule PourMilk
// 4. PourCoffee signals 'WhenAll' on completion
// 5. PourMilk signals 'WhenAll' on completion
// 6. 'WhenAll' resumes the current method from saved state.
// The built-in concurrency control removes the
// complexity of writing a synchronization logic
// of distributed workflows.
}
}
The technology currently matures with early adopters in a closed environment. You can self-start by following these guides:
Cannot find what you are looking for? Ask me a question!
— Serge Semenov
High-level language-integrated abstractions hide a lot of implementation details like:
- Unified Service Communication - both external and internal; HTTP, gRPC, message queues, event streams.
- API specification is auto-generated.
- Pub-Sub is simply declarative in the code.
- Transparent serialization and wire format.
- Stateful workflows are merely methods.
- Minimal learning curve.
- Clean Code up to 5x smaller in size.
- Near-zero cost of programming microservices.
- Delays design decisions.
- No specific framework is needed.
- Exactly once execution for mission-critical apps.
- No error handlers in the code.
- Simplified testing due to the absence of non-functional code.
- Easy API and workflow versioning.
- Unified approach for distributed tracing.
Publications:
- High-level Overview on TheNewStack.io
- The story of development on DZone
- Microservices Best Practices
"Conquest of Distributed Systems":
- Part 1: Business Workflows
- Part 2: Orchestration with Actor Model
- Part 3: Actor Model Hidden in Plain Sight
- Part 4: The Now and the Vision
References:
We believe that every developer deserves the right of creating microservices without using any framework 🤍