LEVELUP 2015 - Enterprise software schaalbaar maken met Service Fabric

LEVELUP 2015 - Enterprise software schaalbaar maken met Service Fabric

hoe AFAS zijn next gen ERP platform ontwikkelt

03dc1d993fa7d5de422c1f35d53f80e6?s=128

Michiel Overeem

November 26, 2015
Tweet

Transcript

  1. Enterprise software schaalbaar maken met Service Fabric hoe AFAS zijn

    next gen ERP platform ontwikkelt
  2. 360+ medewerkers (4 locaties) 10.000 klanten (bedrijven) 1.500.000 gebruikers 80

    miljoen euro omzet (2014) AFAS Software
  3. AFAS Profit Maandelijks 1.500.000 loonstroken HRM, CRM, financieel, order management,

    project management, workflow, ...
  4. 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
  5. Software kun je definiëren in plaats van programmeren.

  6. Modelleer het bedrijf met zijn processen

  7. Sla het model op in een definitie

  8. Lees de definitie in een generator

  9. Lever de resulterende applicatie uit

  10. None
  11. client command-systeem query-systeem event bus

  12. 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; } }
  13. client query-systeem event bus command-systeem

  14. 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; }
  15. client query-systeem event bus

  16. CommandHandler private void Handle(CreateArticleCommand command) { Repository.ExecuteOn<ArticleAggregateRoot>(command.ArticleId, command); } AggregateRootRepository

    - InMemory public virtual async Task ExecuteOn<T>(Guid aggregateId, Command command) where T: AggregateRoot { T aggregateRoot = LoadAggregateRoot<T>(aggregateId); aggregateRoot.Handle(command); await SaveAndDispatchEvents(aggregateRoot); }
  17. 1 3 7 4 8 2 8 6 5 1

    3 1 4 6 2 7 6 5 4 8 3 7 2 5
  18. “Actors are isolated, single-threaded components that encapsulate both state and

    behavior.”
  19. 1 3 7 4 8 2 8 6 5 1

    3 1 4 6 2 7 6 5 4 8 3 7 2 5
  20. service systeem reliable collections communicatie actors service API service systeem

    reliable collections communicatie service API
  21. client query-systeem event bus

  22. AggregateRootRepository - InMemory public 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 Fabric public 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()); }
  23. AggregateRootActor public async Task ExecuteOn(string aggregateRootType, string commandJson) { var

    aggregateRoot = LoadAggregateRoot(aggregateRootType); var command = Deserialize(commandJson); aggregateRoot.Handle(command); await SaveAndDispatchEvents(aggregateRoot); }
  24. één Stateless Actor Type Range partitions voor load balancing Actor

    Id is het Id van de AggregateRoot
  25. client command-systeem event bus query-systeem

  26. 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); }
  27. client command-systeem event bus

  28. client command-systeem event bus

  29. QueryModelBuilder Service public 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(); } }
  30. 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(); } } } }
  31. één Stateful Service Type Named partitions voor load balancing Partition

    name is het type van de QueryModelBuilder
  32. None
  33. 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