A friend of mine was having trouble finding simple but working example of microservices that he could tinker with, finding instead either buzzword-heavy theoretical articles or just samples in weird languages. I decided to prepare a simple project so it can be understood in short amount of time.
The sample project is modeling a “Starbucks-style” coffee ordering process with a cashier, barista and order pick up counter with the following flow:
sequenceDiagram participant Customer participant Cashier participant Barista participant PickUpCounter Customer->>Cashier: Order drink and pay Cashier->>Barista: Relay customer order Note over Barista: prepares the drink Barista->>PickUpCounter:Bring prepared drink Note over PickUpCounter: Announce that the drink is ready
The sample project
For a tech stack, I will be using the awesome MassTransit with RabbitMq as transport and a client process that would send REST requests that “simulate” drink orders.
Here is a quick breakdown of what happens in the sample project
Client process sends random drink orders via POST http requests
Cashier receives client orders via POST request and puts it into a received orders queue (BlockingCollection
) 1
2
3
4
5
6
7
8
9
10
11
12//Nancy module to serve as API for registering orders
public class CashierAPI : NancyModule
{
public CashierAPI(BlockingCollection<Order> ordersQueue)
{
Post("/orders", args =>
{
ordersQueue.Add(this.Bind<Order>()); //put orders into "order queue"
return Response.AsJson(new { Message = "OK" });
});
}
}Then the service registers the order and forwards it as a domain event to the the rest of the services by pushing it to the message bus.
1
2
3
4
5
6
7
8
9
10
11protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
if (_orderQueue.TryTake(out var order, 500))
{
order.WhenReceived = DateTime.UtcNow;
await _messageBus.Publish(order, stoppingToken);
}
else
await Task.Delay(1000, stoppingToken);
}Barista is subscribed to “receive order” domain event, simulate random time working on it then push it to message bus as another domain event
1
2
3
4
5
6
7
8
9
10public async Task Consume(ConsumeContext<Order> context)
{
if (!context.Message.IsComplete)
{
await Task.Delay(_random.Next(500, 3000));
context.Message.WhenCompleted = DateTime.UtcNow;
await _messageBus.Publish(context.Message, CancellationToken.None);
}
}And finally, PickUpCounter would receive the event of completed order and announce it to customers
Note that the actual “business logic” code is interleaved with infrastructure code and there is (almost) no logging and error handling. This is something that obviously shouldn’t happen in production-level code - this stuff is left out for better readability of code.
Getting and running the project
- You can get the project from its Github repository.
- Compiling and running it would require .Net Core 3.0 SDK.
- Running the project depends on local installation of RabbitMq. Take a look here for RabbitMq install instructions.