Dependency Injection

Learn how dependency injection works in Caesar.

Overview

Caesar uses the FX library for dependency injection. FX is a lightweight dependency injection framework for Go. It provides a simple and flexible way to manage dependencies in your application.

The Caesar framework abstracts away the complexities of FX, offering you a simple and elegant way to define and inject dependencies into your controllers and other components.

Providers and Invokers: What's the Difference?

In the realm of Caesar, we have two magical constructs to help us: Providers and Invokers.

Providers

Providers are functions that offer (or provide, hence the name) dependencies. These are the building blocks that Caesar gathers to construct your application's dependency graph.

Invokers

Invokers are functions that are triggered once all Providers have been initialized. Think of them as the "let's get this show on the road" crew. They kick off any necessary startup routines.

Wiring It All Together

Let's dive into an example to see how this works in practice.

Example: Registering Providers and Invokers

Here, we have a config package that sets up our application by registering providers and invokers.

package config

import (
	"citadel/app/controllers"
	authControllers "citadel/app/controllers/auth"
	"citadel/app/drivers"
	dockerDriver "citadel/app/drivers/docker_driver"
	"citadel/app/listeners"
	"citadel/app/repositories"
	"citadel/app/services"
	"citadel/public"
	"log"
	"os"

	"github.com/caesar-rocks/core"
	"github.com/caesar-rocks/events"
)

func ProvideApp(env *EnvironmentVariables) *core.App {
	app := core.NewApp(&core.AppConfig{
		Addr: os.Getenv("ADDR"),
	})

	// Registering providers
	app.RegisterProviders(
		controllers.NewMailDomainsController,
		controllers.NewMailApiKeysController,
		controllers.NewStorageController,
		authControllers.NewCliController,
		// ... add more controllers here
	)

	app.RegisterProviders(
		listeners.NewUsersListener,
		listeners.NewDeploymentsListener,
	)

	app.RegisterProviders(
		services.NewUsersService,
		services.NewAppsService,
		dockerDriver.NewDockerDriver,
	)

	app.RegisterProviders(
		repositories.NewUsersRepository,
		repositories.NewApplicationsRepository,
	)

	app.RegisterProviders(
		RegisterRoutes,
		ProvideStripe,
		ProvideEnvironmentVariables,
		// ... add more global providers here
	)

	// Registering invokers
	app.RegisterInvokers(
		core.ServeStaticAssets(public.FS),
		events.ListenForEvents,
		func(driver drivers.Driver) {
			if err := driver.Init(); err != nil {
				log.Fatal(err)
			}
		},
	)

	return app
}

In the above configuration:

  • We register various controllers, listeners, services, repositories, and other entities as providers.
  • We define invokers that do things like serving static assets and initializing drivers.

Example: Reusing Dependencies in Routes

Once the dependencies are set up, you can reuse them seamlessly. Let’s see how you can define routes using the injected dependencies:

package config

import (
	"citadel/app/controllers"
	authControllers "citadel/app/controllers/auth"
	"citadel/app/middleware"
	"citadel/app/vexillum"
	mailsPages "citadel/views/pages/mails"

	caesarAuth "github.com/caesar-rocks/auth"
	caesar "github.com/caesar-rocks/core"
	"github.com/caesar-rocks/events"
)

func RegisterRoutes(
	auth *caesarAuth.Auth,
	signUpController *authControllers.SignUpController,
	signInController *authControllers.SignInController,
	// ... other controllers
) *caesar.Router {
	router := caesar.NewRouter()

	// Middleware
	router.Use(auth.SilentMiddleware)
	router.Use(middleware.ViewMiddleware(vexillum))

	// Home route
	router.Get("/", func(ctx *caesar.CaesarCtx) error {
		return ctx.Redirect("/apps")
	})

	// Auth routes
	router.Get("/auth/sign_up", signUpController.Show)
	router.Post("/auth/sign_up", signUpController.Handle)

	router.Get("/auth/sign_in", signInController.Show)
	router.Post("/auth/sign_in", signInController.Handle)

	// ... more routes here

	return router
}

Here, dependencies like signUpController and signInController are automatically injected and can be used directly in the route definitions.

Summary

In Caesar, dependency injection (DI) is both powerful and user-friendly. By registering providers, you tell Caesar what dependencies your application needs. Then, invokers jump-start your app with everything wired together perfectly.

If you've successfully read through this documentation without your eyes glazing over, congratulations! You can now inject dependencies like a professional wizard conjuring spells—in a good way, of course.

Feel free to dive into the code, experiment, and create something amazing. After all, with great power (and dependency injection), comes great responsibility!