/golang-dapr

"From Zero to Hero with Go and Dapr" presented at GopherCon 2021

Primary LanguageGoMIT LicenseMIT

From Zero to Hero with Go and Dapr

Slides

This is a Go application demonstrating the key features of Dapr with a few different approaches. My goal is to help you pick the best fit for your needs and level up as a microservices developer.

Dapr, at its core, is a set of building block APIs that abstract away common tasks so you can focus on what matters most-- business value/features. We will focus on the building blocks that I consider as the most useful for Go developers.

  • Publish / Subscribe (with routing)
  • Secret store
  • State store
  • Service Invocation (with discovery and tracing)

Design choices

When building Go microservices, we have many choices to make! gRPC, REST or both? Which HTTP router? How to organize packges?

In this application, packages are organized by purpose/feature. This creates a small hurdle for subscriptions because your application responds with all of the topics in a single callback. To work around this, the subscriptions from each package are merged together into a single response.

You will find examples of "helper code" like this in pkg/dapr. However, be aware that the Go SDK is an abstraction over all of the Dapr APIs. It is up to you to decide on custom code or the SDK.

The Go SDK uses the standard net/http package. To be different, I choose Fiber as the HTTP router for public API traffic and found it to be straightforward to use.

Event callbacks, such as PubSub events, should be considered private communication. Because of this, I highly recommend having Dapr callbacks listen on a separate port that is not publicly exposed. Even better, it should only listen on the loopback interface.

Application flow

With the design decisions out of the way, let's look at the scenarios and specifically how the Dapr building blocks are used.

There are three code paths. Each starts with the receipt of a Pub/Sub event. By default, Dapr uses CloudEvents envelopes to encapsulate event data. This allows Dapr to attach tracing IDs and route the event based on attributes such as type.

The application is an Inventory service, where products are persisted in different stores based on type. Widgets are stored in a PostgreSQL database. Gadgets are stored in a State store, such as Redis or MongoDB. All other product types are stored in a separate service exposed through gRPC.

Demo diagram

By inspecting event.type, Dapr selects one of three routes (URIs) to invoke.

To connect to the PostgreSQL database, the application uses a Secret Store to acquire the needed credentials in order to connect. From there, the jackc/pgx package is used to execute SQL statements.

Gadgets are saved simply by calling the "Save state" operation of the State management API.

Finally, general products are stored in the Products gRPC service. The developer uses the generated gRPC client as normal; however, the endpoint is the Dapr sidecar and an additional dapr-app-id metadata field is attached to the request so Dapr know how to route the request. See this How-To for more details.

All component configurations are located in the components directory. The main Dapr configuration is in config.yaml and is where tracing and preview features are enabled.

Running the demo

After running dapr init, you should have Redis running in a Docker container. You will need to create a PostgreSQL database and update secrets.json accordingly. Then create the widgets table from tables.sql.

I launched Postgres in a container and used pgAdmin to create the golang+dapr database and widgets table.

docker run --name postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres

Start the Products service

make run-products

Start the main Inventory service

In a second terminal run: (Pick the client mode)

make run-custom-http
make run-custom-grpc
make run-sdk-http
make run-sdk-grpc

Send product events

In a third terminal you can publish the 3 product event types. The contents of each message are located in the messages directory.

Send a Widget: This will save in the PostgreSQL database.

make send-widget

Send a Gadget: This will save in the Redis state store.

make send-gadget

Send a Thingamajig: This will invoke the Products service using Dapr for service discovery and mTLS authentication.

make send-thingamajig

That's it!

I hope this was helpful! If you have better ways of handling anything in this sample, please submit a PR! :)