Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Michiel Overeem

November 26, 2015
Tweet

More Decks by Michiel Overeem

Other Decks in Technology

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  5. Software kun je definiëren
    in plaats van programmeren.

    View Slide

  6. Modelleer het bedrijf met
    zijn processen

    View Slide

  7. Sla het model op
    in een definitie

    View Slide

  8. Lees de definitie
    in een generator

    View Slide

  9. Lever de resulterende
    applicatie uit

    View Slide

  10. View Slide

  11. client
    command-systeem
    query-systeem
    event bus

    View Slide

  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; }
    }

    View Slide

  13. client
    query-systeem
    event bus
    command-systeem

    View Slide

  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;
    }

    View Slide

  15. client
    query-systeem
    event bus

    View Slide

  16. 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);
    }

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  20. service systeem
    reliable collections
    communicatie
    actors
    service API
    service systeem
    reliable collections
    communicatie
    service API

    View Slide

  21. client
    query-systeem
    event bus

    View Slide

  22. 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());
    }

    View Slide

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

    View Slide

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

    View Slide

  25. client
    command-systeem
    event bus
    query-systeem

    View Slide

  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);
    }

    View Slide

  27. client
    command-systeem
    event bus

    View Slide

  28. client
    command-systeem
    event bus

    View Slide

  29. 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();
    }
    }

    View Slide

  30. 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();
    }
    }
    }
    }

    View Slide

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

    View Slide

  32. View Slide

  33. 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

    View Slide