Skip to content

Conversation

aengelberg
Copy link
Contributor

@aengelberg aengelberg commented Jul 22, 2025

You can now create a "scoped client", a wrapper of the client that will use the specified context for all operations, including feature flag evaluations and event tracking. This facilitates propagating a context down to all logic that is related to a scoped piece of logic, like a web request.

An LDScopedClient implements most of the same methods as LDClient, but without the context argument.

A scoped client is mutable, to facilitate incrementally building up a multi-context representing the current scope. You can add new contexts (as long as they aren't replacing an existing kind) with LDScopedClient.AddContext, and all contexts added so far will be combined into a multi-context whenever the scoped client is used. LDScopedClient.OverwriteContextByKind is offered as an escape hatch if you need to update existing data.

@aengelberg aengelberg requested a review from a team as a code owner July 22, 2025 21:38
return c.client
}

// Contextual methods: equivalent to calling the same method on the underlying client with the current context
Copy link
Contributor Author

@aengelberg aengelberg Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the remaining methods in this file mirror some existing method on LDClient, but instead of taking a context, use the c.CurrentContext().

All the doc comments are copied over mostly verbatim - I figured this would be better than just See LDClient.MethodName for more information, because once a customer adopts the scoped client, there will be developers in that organization that only ever interact with the scoped client, and they may need quick explainers of each method if it's their first time using LD clients at all.

@aengelberg aengelberg requested a review from mmrj July 22, 2025 21:55
@aengelberg aengelberg changed the title Add LDScopedClient for incrementally building and propagating contexts fix: Add experimental LDScopedClient for incrementally building and propagating contexts Jul 24, 2025
Copy link
Member

@keelerm84 keelerm84 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking pretty good!

func (c *LDScopedClient) AddContext(contexts ...ldcontext.Context) {
c.Lock()
defer c.Unlock()
c.rebuild = true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible all the requested contexts could be dropped (due to conflicts), so you might be prematurely setting this to true. Minor optimization I realize.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm going to keep this the way it is - the "suboptimal" thing will only happen if you're using this function wrong.

Comment on lines 105 to 106
// The scoped client's contexts so far are combined into a multi-context whenever the
// scoped client is used.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm realizing that my mental model is that the scoped client has a single multi-context that it keeps adding to. However, the documentation here is emphasizing that a scoped client has a collection of contexts that are only combined into a multi-context when you call some other method on the client.

Is that difference an implementation detail, or a meaningful distinction that we should continue to clarify?

Copy link
Contributor Author

@aengelberg aengelberg Jul 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that difference an implementation detail, or a meaningful distinction that we should continue to clarify?

@mmrj I'm still torn on this. Right now I think it's a meaningful distinction, because of the mutability. Contexts (and multi-contexts) are supposed to be immutable, so if I say the scoped client contains a multi-context, but also I'm "adding a context" to it, that could raise some questions. I would then clarify I'm not really mutating the multi-context, I'm creating a new multi-context (though not really, because this multi-context is imaginary). And I am mutating the scoped client.

I just updated the LDScopedClient documentation with this paragraph:

// A scoped client contains one or more contexts, each with a unique kind. You
// can add additional contexts with [LDScopedClient.AddContext], as long as the
// new contexts' kinds are not already present. The "current context" is the
// combination of all contexts added so far, as a multi-context.

Then I use the term "current context" whenever I mean the scoped client's current multi-context.

Does that make sense, or just make things more confusing?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes sense to me! thanks for the updates to use "current context" more clearly

@aengelberg aengelberg requested review from mmrj and keelerm84 July 30, 2025 00:25
Copy link
Contributor

@mmrj mmrj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ from Docs for the in-code documentation. I did not review the implementation or tests carefully.

Comment on lines +28 to +36
// A scoped client contains one or more contexts, each with a unique kind. You
// can add additional contexts with [LDScopedClient.AddContext], as long as the
// new contexts' kinds are not already present. The "current context" is the
// combination of all contexts added so far, as a multi-context.
//
// scopedClient := ld.NewScopedClient(client, userContext)
// scopedClient.CurrentContext() // returns the single "user" context
// scopedClient.AddContext(ldcontext.NewWithKind("company", "acme"))
// scopedClient.CurrentContext() // returns a multi-context with a "user" and "company" context
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this example is much clearer now, thanks

// scopedClient := ld.NewScopedClient(client, ldcontext.New("user-key"))
// company := fetchCompanyForUser(user)
// scopedClient.AddContext(ldcontext.NewWithKind("company", company.Id))
// scopedClient.BoolVariation("enterprise-features", false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// scopedClient.BoolVariation("enterprise-features", false)
// scopedClient.BoolVariation("enterprise-features", false) // evaluates the flag using a multi-context with "user" and "company" contexts

optional suggestion

Comment on lines 105 to 106
// The scoped client's contexts so far are combined into a multi-context whenever the
// scoped client is used.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes sense to me! thanks for the updates to use "current context" more clearly

@aengelberg aengelberg changed the title fix: Add experimental LDScopedClient for incrementally building and propagating contexts fix(experimental): Add experimental LDScopedClient for incrementally building and propagating contexts Jul 30, 2025
@aengelberg aengelberg merged commit 2b96332 into v7 Jul 30, 2025
22 checks passed
@aengelberg aengelberg deleted the ae/RO-1653/context-client branch July 30, 2025 22:37
aengelberg pushed a commit that referenced this pull request Aug 14, 2025
🤖 I have created a release *beep* *boop*
---


##
[7.13.2](v7.13.1...v7.13.2)
(2025-08-14)


### Bug Fixes

* **experimental:** Add experimental LDScopedClient for incrementally
building and propagating contexts
([#297](#297))
([2b96332](2b96332))
* **experimental:** Functions for putting/getting an LDScopedClient from
context.Context
([#305](#305))
([d61c744](d61c744))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants