Enterprise software schaalbaar maken met Service Fabric
hoe AFAS zijn next gen ERP platform ontwikkelt
360+ medewerkers (4 locaties)
10.000 klanten (bedrijven)
1.500.000 gebruikers
80 miljoen euro omzet (2014)
AFAS Software
AFAS ProfitMaandelijks 1.500.000 loonstroken
HRM, CRM, financieel, order management, project management, workflow, ...
AFAS Online1780 cores, 25 TB RAM, 180 TB SSD storage
10.000 concurrent RDP gebruikers
200.000 unieke gebruikers per maand
1.500.000 API calls per dag
Commandpublic class CreateArticleCommand : Command{public Guid ArticleId { get; set; }public string Description { get; set; }public decimal Price { get; set; }
}
Eventpublic class ArticleCreatedEvent : Event{public Guid ArticleId { get; set; }public string Description { get; set; } public decimal Price { get; set; }
}
Querypublic class GetArticleQuery : Query{public Guid ArticleId { get; set; }
}
AggregateRoot - Handlepublic void Handle(CreateArticleCommand command){if(command.Price <= 0){throw new CommandValidationException("Price should be greater than 0.");
}if(string.IsNullOrEmpty(command.Description) || command.Description.Length > 20){throw new CommandValidationException("Description is mandatory, " +
"and cannot be longer than 20 characters.");}RaiseEvent(new ArticleCreatedEvent(command.ArticleId, command.Description, command.Price));
}
AggregateRoot - Applyprivate void Apply(ArticleCreatedEvent @event){_saleable = true;_price = @event.Price;
}
CommandHandlerprivate void Handle(CreateArticleCommand command){Repository.ExecuteOn<ArticleAggregateRoot>(command.ArticleId, command);
}
AggregateRootRepository - InMemorypublic virtual async Task ExecuteOn<T>(Guid aggregateId, Command command)
where T: AggregateRoot{T aggregateRoot = LoadAggregateRoot<T>(aggregateId);aggregateRoot.Handle(command);await SaveAndDispatchEvents(aggregateRoot);
}
service systeem
reliable collections
communicatie
actors
service API
service systeem
reliable collections
communicatie
service API
AggregateRootRepository - InMemorypublic virtual async Task ExecuteOn<T>(Guid aggregateId, Command command)
where T: AggregateRoot{T aggregateRoot = LoadAggregateRoot<T>(aggregateId);aggregateRoot.Handle(command);await SaveAndDispatchEvents(aggregateRoot);
}
AggregateRootRepository – Service Fabricpublic override async Task ExecuteOn<T>(Guid aggregateId, Command command)
{
var actor = ActorProxy.Create<IAggregateRootActor>(new ActorId(aggregateId), "App");
await actor.ExecuteOn(typeof(T).AssemblyQualifiedName, command.ToJson());
}
AggregateRootActorpublic async Task ExecuteOn(string aggregateRootType, string commandJson)
{
var aggregateRoot = LoadAggregateRoot(aggregateRootType);
var command = Deserialize(commandJson);
aggregateRoot.Handle(command);
await SaveAndDispatchEvents(aggregateRoot);
}
één Stateless Actor TypeRange partitions voor load balancing
Actor Id is het Id van de AggregateRoot
QueryModelBuilderprivate void Handle(ArticleCreatedEvent @event){Repository.Add(@event.ArticleId, new JObject(
new JProperty("Article", @event.ArticleId),new JProperty("Description", @event.Description),new JProperty("Price", @event.Price)));
}
QueryHandlerprivate JObject Handle(GetArticleQuery query){return Repository.Get(query.ArticleId);
}
QueryModelBuilder Servicepublic async Task Handle(string eventJson)
{
var queue = await StateManager.GetOrAddAsync<IReliableQueue<string>>("qmbQueue");
using(ITransaction tx = StateManager.CreateTransaction())
{
await queue.EnqueueAsync(tx, eventJson);
await tx.CommitAsync();
}
}
QueryModelBuilder Service
protected override async Task RunAsync(CancellationToken cancellationToken)
{
var queue = await StateManager.GetOrAddAsync<IReliableQueue<string>>("qmbQueue");
while(true)
{
using(ITransaction tx = StateManager.CreateTransaction())
{
ConditionalResult<string> dequeueReply = await queue.TryDequeueAsync(tx);
if(dequeueReply.HasValue)
{
string message = dequeueReply.Value;
_queryModelBuilder.Handle(Deserialize(message));
await tx.CommitAsync();
}
}
}
}
één Stateful Service TypeNamed partitions voor load balancing
Partition name is het type van de QueryModelBuilder
Voor vragen, discussie etc: [email protected] - @michielovereem - https://linkedin.com/in/movereem
CQRS + Service Fabric
• Sluit ontzettend goed aan bij onze architectuur.• Betrouwbaarheid, schaalbaarheid en onderhoudsgemak.• Portable cloud hosting is mogelijk.
https://github.com/AFASSoftware/CQRS-Microservices