DDD, CQRS, Event-Driven, Actor Model and Microservices

I want to share my experience in implementing microservice architecture on top of actor-model frameworks such as AKKA and Microsoft Orleans.













My hypothesis: if you use one stack to implement microservices, then you can:







  • Dramatically reduce development complexity and code size
  • Simplify debugging and troubleshooting
  • Simplify deployment
  • Simplify the task of defining service boundaries
  • Get rid of the limitation of the use of stateful services
  • Simplifies the work with distributed transactions.


… « »! , .







?



:







  • : , ;
  • : ;
  • , , .;
  • , :


:







  • : , , .
  • DevOps: , .
  • : , , .
  • stateful-: stateful- .
  • : .


, : , ?







, « » . , « , , .». , !









DDD , (Bounded context).







– , , . , .







, DDD, (entities), - (value-objects). (aggregation roots). , (aggregate identity) .

, / . , , (domain services).

(event bus), (domain events).







y CQRS – «» :







  • (commands) – .
  • (queries) – . CQRS – (command handlers).


(command bus) (query bus) .







, :







  • - (value-objects)
  • (entities)
  • (aggregation roots)
  • (domain services)
  • (aggregate identity)
  • (domain events)
  • (commands)
  • (queries)
  • (command handlers).


«»:







  • ,


, ( ), . (saga) – , , .







Stateful Stateless



(actor model). — .







(actor) – , , stateful , , . . . (, ).







, :







  • (.Net) – Java Scala (C#)
  • Microsoft Orleans – .NET Core (C#)


, , . stateful- .







, stateful-: . .







, Stateful – ! ?







, . , stateless-, .







highload , stateless- – . , ( ?).







, – , stateless-. , , .







.

, , « » (reactive streams), , . , .







?







API-proxy



, , , API-proxy, .







«» WebAPI , swagger , graphql , signalR .









(, , ) – .







, . , , . , . , , .







, , .









.Net Microsoft Orleans. Microsoft Orleans , .. .







:







  1. Framework.Primitives

    , :


  • - (value-objects)
  • (aggregate identity)
  • (domain events)
  • (commands)
  • (queries)


  1. Framework.Domain

    , :

    • (entities)
    • (aggregation roots)
    • (domain services)
    • (command handlers)
  2. Framework.Application .
  3. Framework.Infrastructure
  4. Framework.Host , , , .


:







  1. , . .
  2. , .
  3. , , .
  4. , . (stateless-), , .
  5. .


, , :







  • Service1.Primitives – (, , )
  • Service1.Domain – (, , )
  • Service1.Infrastructure

    (Application) .


, , "" :)

, .







public class UserId : Identity<UserId>
{
    public UserId(System.String value) : base(value) { }
}
[Validator(typeof(BirthValidator))]
public class Birth : SingleValueObject<DateTime>
{
    public Birth(DateTime value) : base(value) { }
}
public class BirthValidator : AbstractValidator<Birth>
{
    public BirthValidator()
    {
        RuleFor(p => p.Value).NotNull();
        RuleFor(p => p.Value).LessThan(DateTime.Now);
    }
}

[Description("  ")]
[HasPermissions(DemoContext.CreateUser, DemoContext.ChangeUser)]
[Validator(typeof(CreateUserCommandValidator))]
public class CreateUserCommand : Command<UserId>
{
    public LocalizedString UserName { get; }
    public Birth Birth { get; }

    public CreateUserCommand(UserId aggregateId, LocalizedString userName, Birth birth) : base(aggregateId)
    {
        UserName = userName;
        Birth = birth;
    }
}

public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
    public CreateUserCommandValidator(IServiceProvider sp)
    {
        RuleFor(p => p.AggregateId).NotNull();
        RuleFor(p => p.UserName).SetValidator(new UserNameValidator());
        RuleFor(p => p.Birth).ApplyRegisteredValidators(sp);

    }
}
[Validator(typeof(BirthValidator))]
public class Birth : SingleValueObject<DateTime>
{
    public Birth(DateTime value) : base(value) { }
}
public class BirthValidator : AbstractValidator<Birth>
{
    public BirthValidator()
    {
        RuleFor(p => p.Value).NotNull();
        RuleFor(p => p.Value).LessThan(DateTime.Now);
    }
}
public class UserState : AggregateState<UserState, UserId>,
    IApply<UserCreatedEvent>,
    IApply<UserRenamedEvent>,
    IApply<UserNotesChangedEvent>,
    IApply<UserTouchedEvent>,
    IApply<ProjectCreatedEvent>,
    IApply<ProjectDeletedEvent>
{
    public LocalizedString Name { get; private set; }
    public Birth Birth { get; private set; }
    public System.String Notes { get; private set; } = System.String.Empty;
    public IEnumerable<Entities.Project> Projects => _projects;

    public void Apply(UserCreatedEvent e) { (Name, Birth) = (e.Name, e.Birth); }
    public void Apply(UserRenamedEvent e) { Name = e.NewName; }
    public void Apply(UserNotesChangedEvent e) { Notes = e.NewValue; }
    public void Apply(UserTouchedEvent _) { }

    public void Apply(ProjectCreatedEvent e)
    {
        _projects.Add(new Entities.Project(e.ProjectId, e.Name));
    }

    public void Apply(ProjectDeletedEvent aggregateEvent)
    {
        var projectToRemove = _projects.FirstOrDefault(i => i.Id == aggregateEvent.ProjectId);
        if (projectToRemove != null) _projects.Remove(projectToRemove);
    }

    private readonly ICollection<Entities.Project> _projects = new List<Entities.Project>();
}
public class UserAggregate : EventDrivenAggregateRoot<UserAggregate, UserId, UserState>,
    IExecute<CreateUserCommand, UserId>,
    IExecute<ChangeUserNotesCommand, UserId>,
    IExecute<TrackUserTouchingCommand, UserId>
{
    public UserAggregate(UserId id) : base(id)
    {
        Command<RenameUserCommand>();
        Command<CreateProjectCommand>();
        Command<DeleteProjectCommand>();
    }

    internal async Task CreateProject(ProjectId projectId, ProjectName projectName)
    {
        await Emit(new ProjectCreatedEvent(Id, projectId, projectName));
    }

    internal async Task DeleteProject(ProjectId projectId)
    {
        await Emit(new ProjectDeletedEvent(Id, projectId));
    }

    public async Task<IExecutionResult> Execute(CreateUserCommand cmd)
    {
        //SecurityContext.Authorized();
        //SecurityContext.HasPermissions(cmd.AggregateId, DemoContext.TestUserPermission);

        await Emit(new UserCreatedEvent(cmd.UserName, cmd.Birth));
        return ExecutionResult.Success();
    }

    public async Task<IExecutionResult> Execute(ChangeUserNotesCommand cmd)
    {
        await Emit(new UserNotesChangedEvent(Id, State.Notes, cmd.NewValue));
        return ExecutionResult.Success();
    }

    public async Task<IExecutionResult> Execute(TrackUserTouchingCommand cmd)
    {
        await Emit(new UserTouchedEvent());
        return ExecutionResult.Success();
    }

    public async Task Rename(UserName newName)
    {
        await Emit(new UserRenamedEvent(newName));
    }
}

      
      





?



:









– -. -, , , . , , .

, .. ( ), API, . .

, (, , , ).









( IDE) .









(" "). .









DDD Event Storming .







stateful-

stateful, stateless . Stateful , , .







Simplify the work with distributed transactions

Stateful out-of-the-box sagas allow you to create distributed transactions.

Some example implementation can be viewed here .








All Articles