-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ftl.Singleton
wrapper around a handle
#1295
Comments
) Fixes issue #1295 --------- Co-authored-by: Alec Thomas <[email protected]>
I know that the
var portableDIDHandle = ftl.Secret[string]("did_web_portable_did")
var bearerDIDHandle = ftl.Map(portableDIDHandle, func(ctx context.Context, sec string) (did.BearerDID, error) {
var pd did.PortableDID
err := json.Unmarshal([]byte(sec), &pd)
if err != nil {
return did.BearerDID{}, fmt.Errorf("failed to unmarshal portable DID: %w", err)
}
bearerDID, err := did.FromPortableDID(pd)
if err != nil {
return did.BearerDID{}, fmt.Errorf("failed to import bearer DID: %w", err)
}
return bearerDID, nil
}) however, if i want to do the same for something that requires multiple resources (specifically secrets or 1 secret and 1 config value), i can't. This is actually the case quite often with vendor API clients that require an API Key and and API Secret. Something like this would be very useful: var vendorAPIKeyHandle = ftl.Secret[string]("vendor_api_key")
var vendorAPISecretHandle = ftl.Secret[string]("vendor_api_secret")
var vendorClientHandle = ftl.Plz(func(ctx context.Context) (vendor.Client, error) {
apiKey := vendorAPIKeyHandle.Get(ctx)
apiSecret := vendorAPISecretHandle.Get(ctx)
return vendor.NewClient(apiKey, apiSecret), nil
}) |
There are a couple of problems with
Another option might be |
Or we could add pair, triple, etc. type Pair[T, U any] struct {
Left T
Right U
}
type PairHandle[T, U any] struct {
left Handle[T]
right Handle[U]
}
func (p PairHandle[T, U]) Get() Pair[T, U] {
return Pair[T, U]{p.left.Get(), p.right.Get()}
}
func PairOf[T, U any](left Handle[T], right Handle[U]) PairHandle[T, U] {
return PairHandle[T, U]{left, right}
} Which might be used like so: var vendorClientHandle = ftl.Map(ftl.PairOf(vendorAPIKeyHandle, vendorAPISecretHandle), func(ctx context.Context, pair Pair[string, string]) (vendor.Client, error) {
return vendor.NewClient(pair.Left, pair.Right), nil
}) |
Random thoughts:
makes sense!
does this apply to both if yes to ^, trying to think about how to avoid global state within a given FTL module without having to rely on receiving an initial request to realize that something is borked: e.g. db connection, vendor api secrets etc. Ideally these sort of checks would happen prior to a module even starting and just insta borking instead of bringing it online and ready to receive requests. I suppose the concept of Outside of FTL, conventionally, projects avoid global state by initializing and performing any necessary checks for all resources in a re: alternative suggestions you've mentioned above Actually thinking about it a bit more now, it may make more sense to store everything related to a given vendor in a single secret that is a json object. thinking back to using AWS Secrets Manager and that's actually how it's done: reference in which case |
For combined state you can also use an FTL data structure directly. type VendorAPICredentials struct {
Key string
Secret string
}
var vendorAPICredentials = ftl.Secret[VendorAPICredentials]("vendorAPICredentials") |
The difference is subtle, but as
I'm not totally convinced that this is a preferable approach. IME debugging initialisation errors is usually more painful than debugging errors after the service is serving. Think of debugging Kubernetes container startup issues - it's usually more painful. Though that is somewhat due to the weird separation between container logs and application logs. A "readiness" function might be a preferable place to evaluate any lazy values like credentials, clients, etc. I'm not necessarily against the idea though, don't get me wrong, but I would like to evaluate different use-cases on a case-by-case basic and see what is the best approach. |
This would provide a context-aware thread-safe singleton value:
This avoids having to write weird wrapper functions to do this for us.
This should return a handle with a
.Get(context.Context)
method for retrieving the actual value. It should panic if the lambda returns an error.The text was updated successfully, but these errors were encountered: