https://github.com/lftk/lcfx
A go.uber.org/fx module for gracefully managing long-running services within the application lifecycle.
https://github.com/lftk/lcfx
asynchronous background-services lifecycle long-running-tasks uber-fx
Last synced: 5 months ago
JSON representation
A go.uber.org/fx module for gracefully managing long-running services within the application lifecycle.
- Host: GitHub
- URL: https://github.com/lftk/lcfx
- Owner: lftk
- License: mit
- Created: 2025-10-10T12:58:17.000Z (8 months ago)
- Default Branch: master
- Last Pushed: 2025-10-12T05:31:06.000Z (8 months ago)
- Last Synced: 2025-11-20T10:04:03.979Z (7 months ago)
- Topics: asynchronous, background-services, lifecycle, long-running-tasks, uber-fx
- Language: Go
- Homepage: https://pkg.go.dev/github.com/lftk/lcfx
- Size: 6.84 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# lcfx
`lcfx` is a small helper library for the [go.uber.org/fx](https://github.com/uber-go/fx) dependency injection framework. It simplifies the management of long-running, asynchronous tasks (like web servers, message queue consumers, or other background services) within the `fx` application lifecycle.
## The Problem
When using `fx`, `OnStart` hooks are expected to complete quickly. If a hook blocks for too long (by default, 15 seconds), the application will time out during startup and exit with an error. Starting a long-running service like an HTTP server directly in an `OnStart` hook will cause this timeout.
The common workaround is to launch the service in a separate goroutine. However, this requires manual wiring for graceful shutdown and a way to signal startup errors back to the main application, which can be complex.
## The Solution: `lcfx`
`lcfx` provides a custom `Lifecycle` wrapper with an `AppendAsync` method. This allows you to write your long-running task logic in a simple, synchronous style, while `lcfx` handles the complexity of running it in the background and ensuring graceful shutdown.
To use it, you need to:
1. Add `lcfx.Module` to your `fx.New()` call. This provides the custom lifecycle to the dependency graph.
2. Request `lcfx.Lifecycle` in your constructor instead of `fx.Lifecycle`.
3. Use the `lc.AppendAsync` method to register your long-running service.
Here is a complete example:
```go
package main
import (
"context"
"errors"
"fmt"
"net/http"
"github.com/lftk/lcfx" // Import lcfx
"go.uber.org/fx"
)
func main() {
app := fx.New(
// 1. Add the lcfx.Module.
lcfx.Module,
fx.Provide(NewMux, NewHandler),
fx.Invoke(Register),
// Disable fx logging for clarity in this example.
fx.NopLogger,
)
app.Run()
}
// 2. Request lcfx.Lifecycle in the constructor.
func NewMux(lc lcfx.Lifecycle) *http.ServeMux {
mux := http.NewServeMux()
server := &http.Server{
Addr: "127.0.0.1:8080",
Handler: mux,
}
// 3. Use lc.AppendAsync for the long-running task.
lc.AppendAsync(fx.Hook{
OnStart: func(context.Context) error {
fmt.Println("Starting HTTP server at", server.Addr)
// Write blocking code directly. lcfx handles the goroutine.
// If this returns an error, lcfx will shut down the app.
err := server.ListenAndServe()
if errors.Is(err, http.ErrServerClosed) {
fmt.Println("HTTP server shut down gracefully.")
return nil // Expected error on graceful shutdown.
}
return err
},
OnStop: func(ctx context.Context) error {
fmt.Println("Stopping HTTP server.")
return server.Shutdown(ctx)
},
})
return mux
}
func NewHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("Got a request.")
fmt.Fprintln(w, "Hello, world!")
})
}
func Register(mux *http.ServeMux, h http.Handler) {
mux.Handle("/", h)
}
```
## Installation
```sh
go get github.com/lftk/lcfx
```