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 , .. .
:
- Framework.Primitives
, :
- - (value-objects)
- (aggregate identity)
- (domain events)
- (commands)
- (queries)
- Framework.Domain
, :
- (entities)
- (aggregation roots)
- (domain services)
- (command handlers)
- Framework.Application .
- Framework.Infrastructure
- Framework.Host , , , .
:
- , . .
- , .
- , , .
- , . (stateless-), , .
- .
, , :
- 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 .