Skip to content
Dispatch Dispatch Dispatch
Go - Getting started

Getting started

Dispatch makes it easy to work with rate limited or unreliable servers, like the one we’ve prepared for this guide. We’re hosting a server that serves random cat facts, but it’s very unreliable and fails with 500 quite often.

cat-facts.dispatch.run

Imagine our application is critically dependant on that crucial cat knowledge. Normally, we’d have to implement retries on request errors or even push this work into a queue. But with Dispatch, all we have to do is wrap the function with a dispatch.Func call and we’ll fetch our cat facts reliably no matter what.

Let’s see how simple it is to build reliable systems with Dispatch and maybe learn a thing or two about cats.

Setup the development environment

CLI is the fastest way to create a Dispatch account and set up local development environment.

It can be installed via Homebrew:

Terminal window
brew tap dispatchrun/dispatch && \
brew install dispatch
Or install from sources with Go
Terminal window
go install github.com/dispatchrun/dispatch@latest

Next, log in to create an account:

Terminal window
dispatch login

login command will open a browser to create a Dispatch account, create an API key and save it on your machine.

If you’re starting from scratch, initialize a Go project:

Terminal window
go mod init

Then install our Go SDK:

Terminal window
go get github.com/dispatchrun/dispatch-go@latest

Create a basic application

Put Dispatch aside for now and create a simple HTTP server that fetches a cat fact from https://cat-facts.dispatch.run and prints it. Create an main.go file and paste the following code:

package main
import (
"context"
"errors"
"io"
"log"
"net/http"
)
func main() {
fetchCatFact := func(ctx context.Context) (any, error) {
response, err := http.Get("https://cat-facts.dispatch.run")
if err != nil {
return nil, err
}
if response.StatusCode != 200 {
return nil, errors.New("cat facts server is down")
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
log.Printf("Cat fact: %s", body)
return nil, nil
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, err := fetchCatFact(r.Context())
if err != nil {
log.Println(err)
}
w.WriteHeader(http.StatusOK)
})
if err := http.ListenAndServe("localhost:8000", nil); err != nil {
log.Fatal(err)
}
}

Start the application:

Terminal window
go run main.go

Send a request to / to confirm it’s working:

Terminal window
curl http://localhost:8000

If it hasn’t failed right away, try sending a few more requests to stumble upon a failure in the cat facts API.

cat facts server is down

This means that fetchCatFact function raised an exception and now the application is left without a cat fact.

Make the application reliable

Fortunately, Dispatch can be added to make any Go function retry on failure and limit the execution rate.

First, import a Dispatch SDK.

package main
import (
"context"
"errors"
"io"
"log"
"net/http"
"github.com/dispatchrun/dispatch-go"
)
func main() {
fetchCatFact := func(ctx context.Context) (any, error) {
response, err := http.Get("https://cat-facts.dispatch.run")
if err != nil {
return nil, err
}
if response.StatusCode != 200 {
return nil, errors.New("failed to fetch a cat fact")
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
log.Printf("Cat fact: %s", body)
return nil, nil
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, err := fetchCatFact(r.Context())
if err != nil {
log.Println(err)
}
w.WriteHeader(http.StatusOK)
})
if err := http.ListenAndServe("localhost:8000", nil); err != nil {
log.Fatal(err)
}
}

Next, set up a handler that Dispatch will call remotely on your server.

package main
import (
"context"
"errors"
"io"
"log"
"net/http"
"github.com/dispatchrun/dispatch-go"
)
func main() {
fetchCatFact := func(ctx context.Context) (any, error) {
response, err := http.Get("https://cat-facts.dispatch.run")
if err != nil {
return nil, err
}
if response.StatusCode != 200 {
return nil, errors.New("failed to fetch a cat fact")
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
log.Printf("Cat fact: %s", body)
return nil, nil
}
endpoint, err := dispatch.New()
if err != nil {
log.Fatal(err)
}
http.Handle(endpoint.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, err := fetchCatFact(r.Context())
if err != nil {
log.Println(err)
}
w.WriteHeader(http.StatusOK)
})
if err := http.ListenAndServe("localhost:8000", nil); err != nil {
log.Fatal(err)
}
}

Next, wrap fetchCatFact in a dispatch.Func call, add it to the Dispatch endpoint and update how fetchCatFact is called. Use the Dispatch method on the fetchCatFact function to call it asynchronously via Dispatch.

package main
import (
"context"
"errors"
"io"
"log"
"net/http"
"github.com/dispatchrun/dispatch-go"
)
func main() {
fetchCatFact := func(ctx context.Context) (any, error) {
fetchCatFact := dispatch.Func(func(ctx context.Context, arg any) (any, error) {
response, err := http.Get("https://cat-facts.dispatch.run")
if err != nil {
return nil, err
}
if response.StatusCode != 200 {
return nil, errors.New("failed to fetch a cat fact")
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
log.Printf("Cat fact: %s", body)
return nil, nil
}
})
endpoint, err := dispatch.New()
endpoint, err := dispatch.New(fetchCatFact)
if err != nil {
log.Fatal(err)
}
http.Handle(endpoint.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, err := fetchCatFact(r.Context())
if err != nil {
log.Println(err)
}
fetchCatFact.Dispatch(r.Context())
w.WriteHeader(http.StatusOK)
})
if err := http.ListenAndServe("localhost:8000", nil); err != nil {
log.Fatal(err)
}
}

Lastly, we need to tell Dispatch that it’s safe to retry fetchCatFact execution when we failed to fetch a cat fact due to API issues.

package main
import (
"context"
"errors"
"io"
"log"
"net/http"
"github.com/dispatchrun/dispatch-go"
)
type CatFactUnavailable struct{}
func (error *CatFactUnavailable) Error() string {
return "failed to fetch a cat fact"
}
func (error *CatFactUnavailable) Temporary() bool {
return true
}
func main() {
fetchCatFact := func(ctx context.Context) (any, error) {
fetchCatFact := dispatch.Func(func(ctx context.Context, arg any) (any, error) {
response, err := http.Get("https://cat-facts.dispatch.run")
if err != nil {
return nil, err
return nil, &CatFactUnavailable{}
}
if response.StatusCode != 200 {
return nil, errors.New("failed to fetch a cat fact")
return nil, &CatFactUnavailable{}
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
return nil, &CatFactUnavailable{}
}
log.Printf("Cat fact: %s", body)
return nil, nil
}
})
endpoint, err := dispatch.New(fetchCatFact)
if err != nil {
log.Fatal(err)
}
http.Handle(endpoint.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, err := fetchCatFact(r.Context())
if err != nil {
log.Println(err)
}
fetchCatFact.Dispatch(r.Context())
w.WriteHeader(http.StatusOK)
})
if err := http.ListenAndServe("localhost:8000", nil); err != nil {
log.Fatal(err)
}
}

That’s all it took to make fetchCatFact retry on request errors and asynchronously execute in background.

Restart the application, but this time use Dispatch CLI:

Terminal window
dispatch run -- go run main.go

Now, try sending a few more GET / requests to this server and see how failures are automatically retried.

This is the magic of Dispatch. It ensures that fetchCatFact is executed successfully no matter what, whether HTTP requests fail or your server has suddenly rebooted.

Under the hood, when fetchCatFact.Dispatch is called, fetchCatFact is not executed immediately. Instead, Dispatch schedules the execution of fetchCatFact and pings back our server when it’s the time to do so. That way, fetchCatFact is executed asynchronously in background. Whenever a failure happens, Dispatch is notified and retries the execution again.