An open API service indexing awesome lists of open source software.

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.

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
```