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

Michiel Overeem (AFAS) - Enterprise software schaalbaar maken met Service Fabric

Embed Size (px)

Citation preview

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

Software kun je definiëren in plaats van programmeren.

Modelleer het bedrijf met zijn processen

Sla het model op in een definitie

Lees de definitie in een generator

Lever de resulterende applicatie uit

clie

ntcommand-systeem

query-systeem

event bus

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

}

clie

nt

query-systeem

event b

us

command-systeem

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;

}

clie

nt

query-systeem

event b

us

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

}

1

3

7

48

2

8

6

5

1

3

1

4

6

2

7

6

5

4

8

3 7

2

5

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

behavior.”

1

3

7

4 8

2

8

6

5

1

3

1

4

6

2

7

6

5

4

8

3 7

2

5

service systeem

reliable collections

communicatie

actors

service API

service systeem

reliable collections

communicatie

service API

clie

nt

query-systeem

event b

us

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

clie

nt

command-systeemeven

t bu

squery-systeem

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

}

clie

nt

command-systeemeven

t bu

s

clie

nt

command-systeemeven

t bu

s

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