Skip to content
Chris's Blog
TwitterLinkedIn

EventStore - Learnings of a broken man

EventStore, Docker4 min read

EventStore and .NET

If you're looking to connect to EventStore from a .NET application, you've got three choices: HTTP, TCP and gRPC. HTTP is available for all langauges that support HTTP requests (so almost all of them). The TCP client is a package only available as a .NET SDK and has now been deprecated by EventStore. Finally, gRPC is the one to choose if you're new. It is the future of EventStore clients and has good support across a range of languages include .NET, Java and Rust(!).

The TCP Client

The gRPC client was only released by EventStore in the last few years. Before that, the best option for .NET developers was the TCP client.
There are various modes of operation for EventStore. You have secure and insecure; single node and cluster. Setting the clients up to work with these different options is fiddly and configuration can be totally different depending on what you're aiming for. In my case, I was attempting to set up a local EventStore instance to work with the current code base without any changes to the code outside of basic socket and credential changes. This ended up requiring a secure cluster setup as no other configuration would have met my requirements (basically, I needed a copy of live), however, for local testing, the recommendation is an insecure single node as this is the least complicated and easiest to set up.

Insecure Single Node Setup

My experience with EventStore's documentation is just plain bad... They're examples don't work, even when followed to the letter and a lot of important information is hidden deep within paragraphs instead of highlighted separately.
There are various ways of running a local EventStore instance, the one I would recommend is spinning up a container using Docker. It's simple and you get repeatable results (unless they remove their Docker image).

Setup with Docker
For the following, you'll need to make sure you have Docker and, by extension, docker-compose installed.

  1. Create a new empty folder and name it something suitable (i.e. EventStoreContainer)
  2. Create a new file and name it docker-compose.yaml
  3. Add the following to the file, in our case, we're going to be running a 20.10.2 instance which is the latest LTS version of EventStore. If you want to use a different version, go to their Docker Hub page and find it's tag for reference there.
1version: '3.4'
2
3services:
4 eventstore.db:
5 image: eventstore/eventstore:20.10.2-buster-slim
6 environment:
7 - EVENTSTORE_CLUSTER_SIZE=1
8 - EVENTSTORE_RUN_PROJECTIONS=All
9 - EVENTSTORE_START_STANDARD_PROJECTIONS=true
10 - EVENTSTORE_EXT_TCP_PORT=1113
11 - EVENTSTORE_EXT_HTTP_PORT=2113
12 - EVENTSTORE_INSECURE=true
13 - EVENTSTORE_ENABLE_EXTERNAL_TCP=true
14 - EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true
15 ports:
16 - "1113:1113"
17 - "2113:2113"
18 volumes:
19 - type: volume
20 source: eventstore-volume-data
21 target: /var/lib/eventstore
22 - type: volume
23 source: eventstore-volume-logs
24 target: /var/log/eventstore
25
26volumes:
27 eventstore-volume-data:
28 eventstore-volume-logs:
  1. Open a command prompt in that directory and run
    docker-compose up
    This will start an instance of EventStore and you should see an output in the terminal window. If you have any issues connecting, this is the place to look.

  2. Go to localhost:2113 and you should now have access to the EventStore dashboard.

TCP Client Configuration

  1. Open a new CLI project in .NET in your editor of choice.
  2. Go to the package manager and include a reference to the EventStore.Client package from NuGet. There will be a few other client options but these are the gRPC clients.
  3. The following code is going to setup a connection with TLS disabled from the client end and append an event to test-stream.
1var settings = ConnectionSettings.Create()
2 .DisableTls();
3
4var conn = EventStoreConnection.Create("ConnectTo=tcp://localhost:1113", settings);
5
6await conn.ConnectAsync();
7
8var data = Encoding.UTF8.GetBytes("{\"a\":\"2\"}");
9var metadata = Encoding.UTF8.GetBytes("{}");
10var evt = new EventData(Guid.NewGuid(), "testEvent", true, data, metadata);
11
12await conn.AppendToStreamAsync("test-stream", ExpectedVersion.Any, evt);

Before we move on, there are a few things of note here. The first is that there are multiple different ways to setup a connection to EventStore from the client that all achieve the same behaviour. This, in my opinion, is hugely confusing and unecessary. Below, I'll list some interchangable variations of line 4 that will all do the same thing.

1var conn = EventStoreConnection.Create(settings.Build(), new Uri("tcp://localhost:1113"))
1var conn = EventStoreConnection.Create("ConnectTo=tcp://localhost:1113;UseSslConnection=false");

Anyway, moving on

  1. When you've run the code, switch back to your localhost:2113 tab and go to the Stream Browser tab (fyi, this would not have been available if we hadn't include the EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true config option in the yaml).
    In that tab, you should see 1 recently created Stream called test-stream with one event inside it. And that's it, you've connected and successfully appended an event.

Secure Cluster Setup

Setting up EventStore as a secure cluster of nodes is what you would expect from a production environment. Ordinarily, the nodes would be on different server instances to achieve load-balancing and high-availability. In our case however, the nodes will be hosted in separate Docker instances on your local machine.

Setup with Docker

  1. Create a new empty folder and name it something suitable (i.e. EventStoreContainer)
  2. Create a new file and name it docker-compose.yaml
  3. Add the following to the file, in our case, we're going to be running a 20.10.2 instance which is the latest LTS version of EventStore. If you want to use a different version, go to their Docker Hub page and find it's tag for reference there.
1version: "3.5"
2
3services:
4 setup:
5 image: eventstore/es-gencert-cli:1.0.2
6 entrypoint: bash
7 user: "1000:1000"
8 command: >
9 -c "mkdir -p ./certs && cd /certs
10 && es-gencert-cli create-ca
11 && es-gencert-cli create-node -out ./node1 -ip-addresses 127.0.0.1,172.30.240.11 -dns-names localhost
12 && es-gencert-cli create-node -out ./node2 -ip-addresses 127.0.0.1,172.30.240.12 -dns-names localhost
13 && es-gencert-cli create-node -out ./node3 -ip-addresses 127.0.0.1,172.30.240.13 -dns-names localhost
14 && find . -type f -print0 | xargs -0 chmod 666"
15 container_name: setup
16 volumes:
17 - ./certs:/certs
18
19 node1.eventstore: &template
20 image: eventstore/eventstore:20.10.2-buster-slim
21 container_name: node1.eventstore
22 env_file:
23 - vars.env
24 environment:
25 - EVENTSTORE_INT_IP=172.30.240.11
26 - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=2111
27 - EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=1111
28 - EVENTSTORE_GOSSIP_SEED=172.30.240.12:2113,172.30.240.13:2113
29 - EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/certs/ca
30 - EVENTSTORE_CERTIFICATE_FILE=/certs/node1/node.crt
31 - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/certs/node1/node.key
32 healthcheck:
33 test:
34 [
35 "CMD-SHELL",
36 "curl --fail --insecure https://node1.eventstore:2113/health/live || exit 1",
37 ]
38 interval: 5s
39 timeout: 5s
40 retries: 24
41 ports:
42 - 1111:1113
43 - 2111:2113
44 volumes:
45 - ./certs:/certs
46 depends_on:
47 - setup
48 restart: always
49 networks:
50 clusternetwork:
51 ipv4_address: 172.30.240.11
52
53 node2.eventstore:
54 <<: *template
55 container_name: node2.eventstore
56 env_file:
57 - vars.env
58 environment:
59 - EVENTSTORE_INT_IP=172.30.240.12
60 - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=2112
61 - EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=1112
62 - EVENTSTORE_GOSSIP_SEED=172.30.240.11:2113,172.30.240.13:2113
63 - EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/certs/ca
64 - EVENTSTORE_CERTIFICATE_FILE=/certs/node2/node.crt
65 - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/certs/node2/node.key
66 healthcheck:
67 test:
68 [
69 "CMD-SHELL",
70 "curl --fail --insecure https://node2.eventstore:2113/health/live || exit 1",
71 ]
72 interval: 5s
73 timeout: 5s
74 retries: 24
75 ports:
76 - 1112:1113
77 - 2112:2113
78 networks:
79 clusternetwork:
80 ipv4_address: 172.30.240.12
81
82 node3.eventstore:
83 <<: *template
84 container_name: node3.eventstore
85 environment:
86 - EVENTSTORE_INT_IP=172.30.240.13
87 - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=2113
88 - EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=1113
89 - EVENTSTORE_GOSSIP_SEED=172.30.240.11:2113,172.30.240.12:2113
90 - EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/certs/ca
91 - EVENTSTORE_CERTIFICATE_FILE=/certs/node3/node.crt
92 - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/certs/node3/node.key
93 healthcheck:
94 test:
95 [
96 "CMD-SHELL",
97 "curl --fail --insecure https://node3.eventstore:2113/health/live || exit 1",
98 ]
99 interval: 5s
100 timeout: 5s
101 retries: 24
102 ports:
103 - 1113:1113
104 - 2113:2113
105 networks:
106 clusternetwork:
107 ipv4_address: 172.30.240.13
108
109networks:
110 clusternetwork:
111 name: eventstoredb.local
112 driver: bridge
113 ipam:
114 driver: default
115 config:
116 - subnet: 172.30.240.0/24
  1. Add a sub-directory to your EventStoreContainer folder called certs

  2. Open a command prompt in that directory and run
    docker-compose up
    This will start an instance of EventStore and you should see an output in the terminal window. If you have any issues connecting, this is the place to look.

  3. After exectuing the above, the es-gencert-cli will generate a root ca and an individual certificate for each node. You don't need much knowledge of certificates here, just an understanding that without them, TLS won't work. Therefore, we're going to need to install them for you current user, even if it's only temporarily.

  4. Open the certs directory and install the ca certificate
    Double click the certificate and select Install Certificate...
    Select Current User and click Next
    Select Place all certificates in the following store and click Browse...
    From the list select Trusted Root Certification Authorities and click OK
    Then click Next and then Finish
    A security warning will pop up, click Yes and the EventStoreDB CA will now be installed

  5. Now, go to https://localhost:2113 and you should have secure access with TLS.

  6. In your command prompt, execute
    certmgr
    This will open up your the certificate manager for your current user. This will allow you to import and remove certificates.