Getting started

Get started with the Dolittle open-source framework

Welcome to the tutorial for Dolittle, where you learn how to write a Microservice that keeps track of foods prepared by the chefs.

After this tutorial you will have:

Use the tabs to switch between the C# and TypeScript code examples. Full tutorial code available on GitHub for C# and TypeScript.

For a deeper dive into our Runtime, check our overview.

Setup

This tutorial expects you to have a basic understanding of C#, .NET and Docker.

Prerequisites:

Setup a .NET Core console project:

$ dotnet new console
$ dotnet add package Dolittle.SDK

This tutorial expects you to have a basic understanding of TypeScript, npm and Docker.

Prerequisites:

Setup a TypeScript NodeJS project using your favorite package manager. For this tutorial we use npm.

$ npm init
$ npm -D install typescript ts-node
$ npm install @dolittle/sdk
$ npx tsc --init --experimentalDecorators --target es6

Create an EventType

First we’ll create an EventType that represents that a dish has been prepared. Events represents changes in the system, a “fact that has happened”. As the event “has happened”, it’s immutable by definition, and we should name it in the past tense accordingly.

An EventType is a class that defines the properties of the event. It acts as a wrapper for the type of the event.

// DishPrepared.cs
using Dolittle.SDK.Events;

[EventType("1844473f-d714-4327-8b7f-5b3c2bdfc26a")]
public class DishPrepared
{
    public DishPrepared (string dish, string chef)
    {
        Dish = dish;
        Chef = chef;
    }

    public string Dish { get; }
    public string Chef { get; }
}

The GUID given in the [EventType()] attribute is the EventTypeId, which is used to identify this EventType type in the Runtime.

// DishPrepared.ts
import { eventType } from '@dolittle/sdk.events';

@eventType('1844473f-d714-4327-8b7f-5b3c2bdfc26a')
export class DishPrepared {
    constructor(readonly Dish: string, readonly Chef: string) {}
}

The GUID given in the @eventType() decorator is the EventTypeId, which is used to identify this EventType in the Runtime.

Create an EventHandler

Now we need something that can react to dishes that have been prepared. Let’s create an EventHandler which prints the prepared dishes to the console.

// DishHandler.cs
using Dolittle.SDK.Events;
using Dolittle.SDK.Events.Handling;
using Microsoft.Extensions.Logging;

[EventHandler("f2d366cf-c00a-4479-acc4-851e04b6fbba")]
public class DishHandler
{
    readonly ILogger _logger;

    public DishHandler(ILogger<DishHandler> logger)
    {
        _logger = logger;
    }

    public void Handle(DishPrepared @event, EventContext eventContext)
    {
        _logger.LogInformation("{Chef} has prepared {Dish}. Yummm!", @event.Chef, @event.Dish);
    }
}

When an event is committed, the Handle() method will be called for all the EventHandlers that handle that EventType.

The [EventHandler()] attribute identifies this event handler in the Runtime, and is used to keep track of which event it last processed, and retrying the handling of an event if the handler fails (throws an exception).

// DishHandler.ts
import { inject } from '@dolittle/sdk.dependencyinversion';
import { EventContext } from '@dolittle/sdk.events';
import { eventHandler, handles } from '@dolittle/sdk.events.handling';
import { Logger } from 'winston';

import { DishPrepared } from './DishPrepared';

@eventHandler('f2d366cf-c00a-4479-acc4-851e04b6fbba')
export class DishHandler {
    constructor(
        @inject('Logger') private readonly _logger: Logger
    ) {}

    @handles(DishPrepared)
    dishPrepared(event: DishPrepared, eventContext: EventContext) {
        this._logger.info(`${event.Chef} has prepared ${event.Dish}. Yummm!`);
    }
}

When an event is committed, the method decorated with the @handles(EventType) for that specific EventType will be called.

The @eventHandler() decorator identifies this event handler in the Runtime, and is used to keep track of which event it last processed, and retrying the handling of an event if the handler fails (throws an exception).

Connect the client and commit an event

Let’s build a client that connects to the Runtime for a Microservice with the id "f39b1f61-d360-4675-b859-53c05c87c0e6". This sample Microservice is pre-configured in the development Docker image.

While configuring the client we register the EventTypes and EventHandlers so that the Runtime knows about them. Then we can prepare a delicious taco and commit it to the EventStore for the specified tenant.

// Program.cs
using Dolittle.SDK;
using Dolittle.SDK.Tenancy;
using Microsoft.Extensions.Hosting;

var host = Host.CreateDefaultBuilder()
    .UseDolittle()
    .Build();

await host.StartAsync();

var client = await host.GetDolittleClient();
await client.EventStore
    .ForTenant(TenantId.Development)
    .CommitEvent(
        content: new DishPrepared("Bean Blaster Taco", "Mr. Taco"),
        eventSourceId: "Dolittle Tacos");

await host.WaitForShutdownAsync();

The string given as eventSourceId is the EventSourceId, which is used to identify where the events come from.

// index.ts
import { DolittleClient } from '@dolittle/sdk';
import { TenantId } from '@dolittle/sdk.execution';

import './DishHandler';
import { DishPrepared } from './DishPrepared';

(async () => {
    const client = await DolittleClient
        .setup()
        .connect();

    const preparedTaco = new DishPrepared('Bean Blaster Taco', 'Mr. Taco');

    await client.eventStore
        .forTenant(TenantId.development)
        .commit(preparedTaco, 'Dolittle Tacos');
})();

The string given in the commit() call is the EventSourceId, which is used to identify where the events come from.

Start the Dolittle environment

Start the Dolittle environment with all the necessary dependencies with the following command:

$ docker run -p 50053:50053 -p 51052:51052 -p 27017:27017 -d dolittle/runtime:latest-development

This will start a container with the Dolittle Development Runtime on port 50053 and 51052 and a MongoDB server on port 27017. The Runtime handles committing the events and the event handlers while the MongoDB is used for persistence.

Run your microservice

Run your code, and get a delicious serving of taco:

$ dotnet run
info: Dolittle.SDK.DolittleClientService[0]
      Connecting Dolittle Client
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: .../GettingStarted
info: Dolittle.SDK.Events.Processing.EventProcessors[0]
      EventHandler f2d366cf-c00a-4479-acc4-851e04b6fbba registered with the Runtime, start handling requests
info: DishHandler[0]
      Mr. Taco has prepared Bean Blaster Taco. Yummm!

$ npx ts-node index.ts
info: EventHandler f2d366cf-c00a-4479-acc4-851e04b6fbba registered with the Runtime, start handling requests.
info: Mr. Taco has prepared Bean Blaster Taco. Yummm!

Check the status of your microservice

With everything is up and running you can use the Dolittle CLI to check what’s going on.

Open a new terminal.

Now you can list the registered event types with the following command:

$ dolittle runtime eventtypes list
EventType
------------
DishPrepared

And check the status of the event handler with the following commands:

$ dolittle runtime eventhandlers list
EventHandler  Scope    Partitioned  Status
------------------------------------------
DishHandler   Default  ✅            ✅

$ dolittle runtime eventhandlers get DishHandler
Tenant                                Position  Status
------------------------------------------------------
445f8ea8-1a6f-40d7-b2fc-796dba92dc44  1

What’s next