open-feature / go-sdk

Go SDK for OpenFeature
https://openfeature.dev
Apache License 2.0
138 stars 33 forks source link

[FEATURE] Implement transaction context #256

Closed lukas-reining closed 1 week ago

lukas-reining commented 8 months ago

We should implement transaction context propagation

What is transaction context?

From the spec: Transaction context is a container for transaction-specific evaluation context (e.g. user id, user agent, IP). Transaction context can be set where specific data is available (e.g. an auth service or request handler) and by using the transaction context propagator it will automatically be applied to all flag evaluations within a transaction (e.g. a request or thread).

Transaction context has been added in the following PR: https://github.com/open-feature/spec/pull/227

Examples:

Implementation

The JS SDK implementation is implemented as a POC that is currently being hardened. https://github.com/open-feature/js-sdk/blob/main/packages/server/src/transaction-context/transaction-context.ts

Usage

An example usage for the JS SDK implementation of the feature is the usage for propagating HTTP request information in the NestJS SDK. https://github.com/open-feature/js-sdk/blob/main/packages/nest/src/evaluation-context-interceptor.ts#L31

yardenlaif commented 6 months ago

Is it expected that the user pass down the context? Because I'm not sure there's a trivial way in Go to have a context for a function call which isn't passed to it. It's possible to create a context.Context which has our TransactionContext as a value and require the user to use the context.Context when getting the feature flag:

// Creating the context
ctx := NewTransactionContext(context.Background(), map[string]int{"userID": 12345})
next(ctx)

// Using the context
client.BooleanValue(ctx, flag, defaultValue)

The implementation would be something like:

func NewTransactionContext(ctx context.Context, attributes map[string]interface{}) {
    return context.WithValue(ctx, "transactioncontext", attributes)
}

func (c *Client) BooleanValue(ctx context.Context, flag string, defaultValue bool, options ...Option) {
    transactionContext := ctx.Value("transactioncontext")
   ...
}
Kavindu-Dodan commented 6 months ago

@yardenlaif thanks for the interest in this issue :)

Is it expected that the user pass down the context?

Yes, context should be expected to be provided by the user. This maps with our current flag evaluation methods.

It's possible to create a context.Context which has our TransactionContext

+1 and we should expose it from openfeature api [1]

  // Derive transaction context
 openfeature.SetTransactionContext(ctx, value)

Regarding the key, use an empty struct instead of a string. For example, context.WithValue(ctx, key{}, "bla") where key is an empty struct to avoid hijacking.

[1] - https://github.com/open-feature/go-sdk?tab=readme-ov-file#usage

beeme1mr commented 6 months ago

Thanks @Kavindu-Dodan! @lukas-reining @thomaspoignant do you have anything to add?

lukas-reining commented 6 months ago

To me this looks like a plan @beeme1mr @Kavindu-Dodan @yardenlaif :)

toddbaert commented 6 months ago

Yep, agreed. I think this is the idiom I would expect in Go.

toddbaert commented 1 week ago

Completed with https://github.com/open-feature/go-sdk/pull/283