Open gaby opened 4 months ago
On the applications I currently use Fiber in production, we create a whole set of application components using a single pointer of structs, and perform the Dependency Injection at the bootstrap of the application to provide this "Application State". I will give an example on how we perform it today:
func main() {
app := fiber.New()
service := UserService{}
handler := UserHandler{
service: service,
}
app.Get("/users/:id", handler.GetUser)
app.Listen(":3000")
}
type User struct{}
type UserService struct {
}
func (*UserService) GetUser(id string) (*User, error) {
return nil, nil
}
type UserHandler struct{
service UserService
}
func (handler *UserHandler) GetUser(ctx fiber.Ctx) error {
_, _ = handler.service.GetUser(ctx.Get(":id"))
return nil
}
In any case, such solution (Fiber's Application State) could help us improve the solution we have today.
Hi from the Testcontainers for Go community 👋 ! Core maintainer here 🙋
We have a workshop for learning the fundamentals of Testcontainers in the Go language, and we have what we call local development mode, in which we could use the build to start the runtime dependencies (databases, queues, cloud services...) selectively and only when certain build tags (e.g. -tags=dev
) are passed to the Go toolchain. Then, the dependencies in those "protected-by-build-tags" file will be only added to the final binary if and only if the dev mode build tags are used, not ending up in the production binary.
I think this approach could be combined with the built-in capabilities in GoFiber, to start these dependencies in a hook only when the build adds the right build tags.
You can check it here: https://github.com/testcontainers/workshop-go/blob/main/step-4-dev-mode-with-testcontainers.md
What are your thoughts on this?
@mdelapenya Does this require adding testcontainers as a dependency in Fiber go.mod
? Or is there a way to keep it separate.
I'm afraid I'm not an expert in the Fiber ecosistem yet, but I'd say that, if we are able to create Go modules for them so that only those using the "hooks" (or whatever implementation we use) will receive the dependencies. In the end, they will be dev-time dependencies, so the production build won't receive them.
@gaby I'd expect Fiber providing a "RuntimeDependency" interface with at least the Start and Stop methods, so that this hook could control the lifecycle of those startable dependencies. An implementer of that interface (e.g. a hook for testcontainers-go module) would contribute runtime dependencies (databases, queues, cloud services...) to some state in the framework, so that the framework can handle their lifecycles (start & stop when needed).
Something like:
package fiber
import "context"
type RuntimeDependency interface {
Start(context.Context) error
Terminate(context.Context) error
}
// hasDependencies Checks if there are any dependency for the current application.
func (app *App) hasDependencies() bool {
return len(app.runtimeDependencies) > 0
}
// startDependencies Handles the startup process of dependencies for the current application.
// Iterates over all dependencies and starts them, panics if any error occurs.
func (app *App) startDependencies() {
if app.hasDependencies() {
for _, dep := range app.runtimeDependencies {
err := dep.Start(app.newCtx().Context())
if err != nil {
panic(err)
}
}
}
}
// shutdownDependencies Handles the shutdown process of dependencies for the current application.
// Iterates over all dependencies and terminates them, panics if any error occurs.
func (app *App) shutdownDependencies() {
if app.hasDependencies() {
for _, dep := range app.runtimeDependencies {
err := dep.Terminate(app.newCtx().Context())
if err != nil {
panic(err)
}
}
}
}
And in app.go:
// App denotes the Fiber application.
type App struct {
...
// runtimeDependencies is a list of dependencies that are used by the app (e.g. databases, caches, etc.)
runtimeDependencies []RuntimeDependency
}
Calling app.shutdownDependencies()
and app.startDependencies()
where it better fits.
Quick and dirty idea, as I'm talking without knowing any about the fiber codebase yet, so please correct me if this is nonsense 😅
@mdelapenya Yeah that approach makes sense, I will add it to the idea pool
On the applications I currently use Fiber in production, we create a whole set of application components using a single pointer of structs, and perform the Dependency Injection at the bootstrap of the application to provide this "Application State". I will give an example on how we perform it today:
func main() { app := fiber.New() service := UserService{} handler := UserHandler{ service: service, } app.Get("/users/:id", handler.GetUser) app.Listen(":3000") } type User struct{} type UserService struct { } func (*UserService) GetUser(id string) (*User, error) { return nil, nil } type UserHandler struct{ service UserService } func (handler *UserHandler) GetUser(ctx fiber.Ctx) error { _, _ = handler.service.GetUser(ctx.Get(":id")) return nil }
In any case, such solution (Fiber's Application State) could help us improve the solution we have today.
I think it is enough. An app-level state manager may not display the benefit. But now I commit to a rust web server, axum provide a similar impl, but it enforces the handler is a stateless function
Feature Proposal Description
This proposal aims to introduce Application State Management feature in Fiber to enable the sharing of stateful data across middleware and request handlers efficiently. This is a feature supported in other language frameworks for example: Starlette and FastAPI.
Sources:
Alignment with Express API
In fiber these proposal is similar to
ctx.Locals
but applies to the whole application. Resources stored in thestate
can be accessed by multiple handlers, middlewares, etc.In Express.js it's similar to
app.locals
, but in express the values can only be used when rendering templates.HTTP RFC Standards Compliance
N/a
API Stability
API Methods
MustSet
, to panic if key already exists?We could also add specific
Get
functions for specific common types: int, string, float, etc. For example, for getting a string from the app.State:It would be the responsibility of the developer to call to appropriate function.
Feature Examples
Checklist: