Core Module
Overview
Yokai provides a fxcore module, the heart of your applications
.
It comes with:
- a bootstrapper
- a dependency injection system, based on Fx
- a dedicated core HTTP server
- pre-enabled config, health check, log, trace, metrics and generate modules
- an extension system for Yokai
built-in
, contrib or yourown
modules
The core HTTP server
runs automatically on a dedicated port (default 8081
), to serve:
- the dashboard: UI to get an overview of your application
- the debug endpoints: to expose information about your build, config, loaded modules, etc.
- the health check endpoints: to expose the configured health check probes of your application
- the metrics endpoint: to expose all collected metrics from your application
Whatever your type of application (HTTP, gRPC, worker, etc.), all platform concerns
are handled by this
dedicated server:
- to avoid to expose sensitive information (health checks, metrics, debug, etc.) to your users
- and most importantly to enable your application to focus on its logic
Installation
When you use a Yokai application template
, you have nothing to install, it's ready to use.
Configuration
modules:
core:
server:
address: ":8081" # core http server listener address (default :8081)
errors:
obfuscate: false # to obfuscate error messages on the core http server responses
stack: false # to add error stack trace to error response of the core http server
dashboard:
enabled: true # to enable the core dashboard
overview:
app_description: true # to display the app description on the dashboard overview
app_env: true # to display the app env on the dashboard overview
app_debug: true # to display the app debug on the dashboard overview
app_version: true # to display the app version on the dashboard overview
log_level: true # to display the log level on the dashboard overview
log_output: true # to display the log output on the dashboard overview
trace_sampler: true # to display the trace sampler on the dashboard overview
trace_processor: true # to display the trace processor on the dashboard overview
log:
headers: # to log incoming request headers on the core http server
x-foo: foo # to log for example the header x-foo in the log field foo
x-bar: bar
exclude: # to exclude specific routes from logging
- /healthz
- /livez
- /readyz
- /metrics
level_from_response: true # to use response status code for log level (ex: 500=error)
trace:
enabled: true # to trace incoming request headers on the core http server
exclude: # to exclude specific routes from tracing
- /healthz
- /livez
- /readyz
- /metrics
metrics:
expose: true # to expose metrics route, disabled by default
path: /metrics # metrics route path (default /metrics)
collect:
enabled: true # to collect core http server metrics, disabled by default
namespace: foo # core http server metrics namespace (empty by default)
buckets: 0.1, 1, 10 # to override default request duration buckets
normalize:
request_path: true # to normalize http request path, disabled by default
response_status: true # to normalize http response status code (2xx, 3xx, ...), disabled by default
healthcheck:
startup:
expose: true # to expose health check startup route, disabled by default
path: /healthz # health check startup route path (default /healthz)
readiness:
expose: true # to expose health check readiness route, disabled by default
path: /readyz # health check readiness route path (default /readyz)
liveness:
expose: true # to expose health check liveness route, disabled by default
path: /livez # health check liveness route path (default /livez)
debug:
config:
expose: true # to expose debug config route
path: /debug/config # debug config route path (default /debug/config)
pprof:
expose: true # to expose debug pprof route
path: /debug/pprof # debug pprof route path (default /debug/pprof)
routes:
expose: true # to expose debug routes route
path: /debug/routes # debug routes route path (default /debug/routes)
stats:
expose: true # to expose debug stats route
path: /debug/stats # debug stats route path (default /debug/stats)
build:
expose: true # to expose debug build route
path: /debug/build # debug build route path (default /debug/build)
modules:
expose: true # to expose debug modules route
path: /debug/modules/:name # debug modules route path (default /debug/modules/:name)
Notes:
- the core HTTP server requests logging will be based on the log module configuration
- the core HTTP server requests tracing will be based on the trace module configuration
- if
app.debug=true
(or env varAPP_DEBUG=true
):- the dashboard will be automatically enabled
- all the debug endpoints will be automatically exposed
- error responses will not be obfuscated and stack trace will be added
Usage
Bootstrap
When you use a Yokai application template, a internal/bootstrap.go
file is provided.
This is where you can:
- load Yokai
built-in
, contrib or yourown
modules - configure the application with any
fx.Option
, at bootstrap on runtime
Example of bootstrap loading the HTTP server module:
package internal
import (
"context"
"fmt"
"testing"
"github.com/ankorstore/yokai/fxcore"
"github.com/ankorstore/yokai/fxhttpserver"
"go.uber.org/fx"
)
func init() {
RootDir = fxcore.RootDir(1)
}
// RootDir is the application root directory.
var RootDir string
// Bootstrapper can be used to load modules, options, dependencies, routing and bootstraps your application.
var Bootstrapper = fxcore.NewBootstrapper().WithOptions(
// modules registration
fxhttpserver.FxHttpServerModule,
// dependencies registration
Register(),
// routing registration
Router(),
)
// Run starts the application, with a provided [context.Context].
func Run(ctx context.Context) {
Bootstrapper.WithContext(ctx).RunApp()
}
// RunTest starts the application in test mode, with an optional list of [fx.Option].
func RunTest(tb testing.TB, options ...fx.Option) {
tb.Helper()
tb.Setenv("APP_CONFIG_PATH", fmt.Sprintf("%s/configs", RootDir))
Bootstrapper.RunTestApp(tb, fx.Options(options...))
}
Notes:
- the
Run()
function is used to start your application. - the
RunTest()
function can be used in your tests, to start your application in test mode
Dependency injection
Yokai is built on top of Fx, offering a simple yet powerful dependency injection system
.
This means you don't have to worry about injecting dependencies to your structs, your just need to register their constructors, and Yokai will automatically autowire them at runtime.
For example, if you create an ExampleService
that has the *config.Config as dependency:
package service
import (
"fmt"
"github.com/ankorstore/yokai/config"
)
type ExampleService struct {
config *config.Config
}
func NewExampleService(config *config.Config) *ExampleService {
return &ExampleService{
config: config,
}
}
func (s *ExampleService) PrintAppName() {
fmt.Printf("name: %s", s.config.AppName())
}
You then need to register it, by providing its constructor in internal/register.go
:
package internal
import (
"github.com/foo/bar/internal/service"
"go.uber.org/fx"
)
func Register() fx.Option {
return fx.Options(
// register the ExampleService
fx.Provide(service.NewExampleService),
// ...
)
}
This will make the ExampleService
available in Yokai's dependency injection system, with its dependency on *config.Config
autowired.
The ExampleService
will also be available for injection in any constructor depending on it.
Dashboard
If modules.core.server.dashboard=true
, the core dashboard is available on the port 8081
:
From there, you can get:
- an overview of your application
- information and tooling about your application: build, config, metrics, pprof, etc.
- access to the configured health check endpoints
- access to the loaded modules information (when exposed)
The core dashboard is made for development
purposes.
But since it's served on a dedicated port, you can safely decide to leave it enabled on production, to not expose it to the public, and access it via port forward for example.
Testing
You can start your application in test mode, with the RunTest()
function provided in the bootstrapper.
This wil automatically set the env var APP_ENV=test
, and merge your test config.
It accepts a list of fx.Option
, for example:
fx.Populate()
to extract from the test application autowired components for your testsfx.Invoke()
to execute a function at runtimefx.Decorate()
to override componentsfx.Replace()
to replace components- etc.
Test example with fx.Populate()
:
package internal_test
import (
"testing"
"github.com/foo/bar/internal/service"
"github.com/stretchr/testify/assert"
"go.uber.org/fx"
)
func TestExample(t *testing.T) {
var exampleService *service.ExampleService
// run app in test mode and extract the ExampleService
internal.RunTest(t, fx.Populate(&exampleService))
// assertion example
assert.Equal(t, "foo", exampleService.Foo())
}
See Fx documentation for the available fx.Option
.