Slide 1

Slide 1 text

Enterprise software schaalbaar maken met Service Fabric hoe AFAS zijn next gen ERP platform ontwikkelt

Slide 2

Slide 2 text

360+ medewerkers (4 locaties) 10.000 klanten (bedrijven) 1.500.000 gebruikers 80 miljoen euro omzet (2014) AFAS Software

Slide 3

Slide 3 text

AFAS Profit Maandelijks 1.500.000 loonstroken HRM, CRM, financieel, order management, project management, workflow, ...

Slide 4

Slide 4 text

AFAS Online 1780 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

Slide 5

Slide 5 text

Software kun je definiëren in plaats van programmeren.

Slide 6

Slide 6 text

Modelleer het bedrijf met zijn processen

Slide 7

Slide 7 text

Sla het model op in een definitie

Slide 8

Slide 8 text

Lees de definitie in een generator

Slide 9

Slide 9 text

Lever de resulterende applicatie uit

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

client command-systeem query-systeem event bus

Slide 12

Slide 12 text

Command public class CreateArticleCommand : Command { public Guid ArticleId { get; set; } public string Description { get; set; } public decimal Price { get; set; } } Event public class ArticleCreatedEvent : Event { public Guid ArticleId { get; set; } public string Description { get; set; } public decimal Price { get; set; } } Query public class GetArticleQuery : Query { public Guid ArticleId { get; set; } }

Slide 13

Slide 13 text

client query-systeem event bus command-systeem

Slide 14

Slide 14 text

AggregateRoot - Handle public 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 - Apply private void Apply(ArticleCreatedEvent @event) { _saleable = true; _price = @event.Price; }

Slide 15

Slide 15 text

client query-systeem event bus

Slide 16

Slide 16 text

CommandHandler private void Handle(CreateArticleCommand command) { Repository.ExecuteOn(command.ArticleId, command); } AggregateRootRepository - InMemory public virtual async Task ExecuteOn(Guid aggregateId, Command command) where T: AggregateRoot { T aggregateRoot = LoadAggregateRoot(aggregateId); aggregateRoot.Handle(command); await SaveAndDispatchEvents(aggregateRoot); }

Slide 17

Slide 17 text

1 3 7 4 8 2 8 6 5 1 3 1 4 6 2 7 6 5 4 8 3 7 2 5

Slide 18

Slide 18 text

“Actors are isolated, single-threaded components that encapsulate both state and behavior.”

Slide 19

Slide 19 text

1 3 7 4 8 2 8 6 5 1 3 1 4 6 2 7 6 5 4 8 3 7 2 5

Slide 20

Slide 20 text

service systeem reliable collections communicatie actors service API service systeem reliable collections communicatie service API

Slide 21

Slide 21 text

client query-systeem event bus

Slide 22

Slide 22 text

AggregateRootRepository - InMemory public virtual async Task ExecuteOn(Guid aggregateId, Command command) where T: AggregateRoot { T aggregateRoot = LoadAggregateRoot(aggregateId); aggregateRoot.Handle(command); await SaveAndDispatchEvents(aggregateRoot); } AggregateRootRepository – Service Fabric public override async Task ExecuteOn(Guid aggregateId, Command command) { var actor = ActorProxy.Create(new ActorId(aggregateId), "App"); await actor.ExecuteOn(typeof(T).AssemblyQualifiedName, command.ToJson()); }

Slide 23

Slide 23 text

AggregateRootActor public async Task ExecuteOn(string aggregateRootType, string commandJson) { var aggregateRoot = LoadAggregateRoot(aggregateRootType); var command = Deserialize(commandJson); aggregateRoot.Handle(command); await SaveAndDispatchEvents(aggregateRoot); }

Slide 24

Slide 24 text

één Stateless Actor Type Range partitions voor load balancing Actor Id is het Id van de AggregateRoot

Slide 25

Slide 25 text

client command-systeem event bus query-systeem

Slide 26

Slide 26 text

QueryModelBuilder private 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))); } QueryHandler private JObject Handle(GetArticleQuery query) { return Repository.Get(query.ArticleId); }

Slide 27

Slide 27 text

client command-systeem event bus

Slide 28

Slide 28 text

client command-systeem event bus

Slide 29

Slide 29 text

QueryModelBuilder Service public async Task Handle(string eventJson) { var queue = await StateManager.GetOrAddAsync>("qmbQueue"); using(ITransaction tx = StateManager.CreateTransaction()) { await queue.EnqueueAsync(tx, eventJson); await tx.CommitAsync(); } }

Slide 30

Slide 30 text

QueryModelBuilder Service protected override async Task RunAsync(CancellationToken cancellationToken) { var queue = await StateManager.GetOrAddAsync>("qmbQueue"); while(true) { using(ITransaction tx = StateManager.CreateTransaction()) { ConditionalResult dequeueReply = await queue.TryDequeueAsync(tx); if(dequeueReply.HasValue) { string message = dequeueReply.Value; _queryModelBuilder.Handle(Deserialize(message)); await tx.CommitAsync(); } } } }

Slide 31

Slide 31 text

één Stateful Service Type Named partitions voor load balancing Partition name is het type van de QueryModelBuilder

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

Voor vragen, discussie etc: m.overeem@afas.nl - @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