Skip to content

Part 2: Run comby

comby is a powerful framework that offers the user many options. On the one hand, the so-called defaults from comby can be used, which offer the user ready-made domains such as Tenant, Account, Groups, Admin Dashboard and so on or the user is completely free to start from scratch. In this guide, we will show you how to start comby with the defaults.

TIP

Further information on the options for configuring comby as needed can be found in the documentation. The starting point is: Documentation

Minimal Example - completely blank

go
package main

import (
	"fmt"

	"github.com/gradientzero/comby/v2"
)

func main() {
	fc, _ := comby.NewFacade()
	fmt.Println(fc.PrintInfo())
	// notice: application will exit immediately with the following message:

	//     _______  _____  _______ ______  __   __
	//     |       |     | |  |  | |_____]   \_/   » x.y.z
	//     |_____  |_____| |  |  | |_____]    |    » darwin » arm64

	//  » Working Directory..... /path/to/my/working/directory
	//  » App Name.............. app
	//  » Event Store........... memory
	//  » Command Store......... memory
	//  » Cache Store........... memory
	//  » Data Store............ memory
	//  » Broker................ disabled
	//  » Email................. disabled
}

Even the output to the console is optional, but this just shows how to create a new facade. However, not much is happening here yet. Usually there is a listener - for example an HTTP listener - that ensures that the application can accept and process HTTP requests. The newly created facade can then be tackled.

Simple Example - with defaults

go
// simple/main.go
package main

import (
	"context"
	"fmt"
	"net/http"

	"comby.io/examples/simple/api"
	"comby.io/examples/simple/domain"
	"github.com/danielgtaylor/huma/v2"
	"github.com/danielgtaylor/huma/v2/adapters/humago"
	"github.com/gradientzero/comby/v2"
	combyApi "github.com/gradientzero/comby/v2/api"
	combyDomain "github.com/gradientzero/comby/v2/domain"
	"github.com/gradientzero/comby/v2/web"
)

func main() {
	// create empty facade
	fc, _ := comby.NewFacade()

	// register comby domains
	if err := combyDomain.RegisterDefaults(context.Background(), fc); err != nil {
		panic(err)
	}

	// register my domains
	if err := domain.RegisterDomains(context.Background(), fc); err != nil {
		panic(err)
	}

	// restore state
	if err := fc.RestoreState(); err != nil {
		panic(err)
	}

	// create new mux
	mux := http.NewServeMux()

	// add comby admin dashboard
	mux.Handle("/admin/", web.AdminHandler())

	// add custom router handleFunc
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`hello world`))
	})

	// create huma api
	humaApi := humago.New(mux, huma.DefaultConfig("My API", "1.0.0"))

	// add comby's default api
	if err := combyApi.RegisterDefaults(fc, humaApi); err != nil {
		panic(err)
	}

	// add our custom api
	if err := api.RegisterEndpoints(fc, humaApi); err != nil {
		panic(err)
	}

	fmt.Println(fc.PrintInfo())
	http.ListenAndServe("127.0.0.1:8080", mux)
}

There are countless variations between the minimal version and a version from a real-world application. In this example we are just using memory storage for the Events and Commands. The so called defaults are used here (combyDomain.RegisterDefaults and combyApi.RegisterDefaults), which means that the all predefined domains like Tenant, Account, Group, Authorization, Admin Dashboard and many others are available out of the box. In addition we register our own, custom domains to the Facade (domain.RegisterDomains). The Facade is the central point of the application. Once all domains are registered we can restore the state of the application from the event store (fc.RestoreState).

Since we want to use a REST API by default, we continue with creating the router. comby internally uses huma, which enables the generation of OpenAPI 3.1. This is used in real examples to generate TypeScript clients. So after we register the corresponding comby api defaults(combyApi.RegisterDefaults), we can continue with our own api endpoints (api.RegisterEndpoints).

Since we use huma in both cases, a common REST API is generated and is available at runtime as http://localhost:8080/api/openapi.yaml. This is automatically used to provide an interactive REST API Explorer at http://localhost:8080/docs/api. We also connect the comby Admin Dashboard and can access it via http://localhost:8080/admin.

At the initial start, predefined credentials that can also be overwritten by environment variables are displayed:

go
...
-----------------------------------------------
 email:         admin@comby.io
 password:      Placeholder@1
-----------------------------------------------

You can use them to login into the Admin Dashboard. By using Dev Tools you can also extract the Authorization Header, which is a Bearer Token. This token can be used to authenticate against the REST API Explorer.

INFO

We have not setup any email provider, nor any other external services. In a real world example, you would use a persistent storage like SQLite or Postgres. Further information about Configuration can be found in the documentation: Configuration

go
eventStore = store.NewEventStoreSQLite("/path/to/sqlite/file.db")
eventStore = store.NewEventStorePostgres("host", port, "user", "password", "db")

Let's create a new domain using an example and learn how comby works.