-
Notifications
You must be signed in to change notification settings - Fork 139
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
feat(parameters): AppConfigProvider #1200
feat(parameters): AppConfigProvider #1200
Conversation
…-provider' into 1177-feature-implement-appconfig-provider
@dreamorosi I need help with types for TL;DR; To retrieve configuration two API two calls are necessary. First to
When you make the first call to API you get initial token that encapsulates all these fields. Then you make the next API call with the only one initial token to I placed the last two properties into
Right now, It also makes sense to "reset" the token, when you receive SDK options to overwrite in Another way is not to require options for a new instance of the class, but raise an error for the first call of the Sorry for the delay with the changes. We can discuss it in discord to speed up the process. |
Hey Sergei, thanks for the message and please no need to apologise for the times, you're doing great - no complaints at all here 😄 Let me pull the code and play with it in my IDE. Before giving an answer I need to better understand it and also wrap my head around the AppConfig flow. Doing this now, you can expect an answer in the next couple of hours tops. |
Thanks 👍 |
Thanks for the diagram, I like the idea of being able to retrieve multiple configurations! I have been playing with the SDK, reading the docs, and making a few tests and just like you proposed I think we should support this use case: const provider = new AppConfigProvider( {
sdkOptions: {
application: 'TestApplication',
environment: 'Lambda',
}
});
const config = await provider.get('TestFeatureFlag');
const otherConfig = await provider.get('TestFreeForm'); Based on the way the SDK & service work, and as you also figured out, each session token is tied to a set of:
This means that while 1 & 2 stay constant, if we want to retrieve two different configurations we should request a new session token. At least based on my tests, if you use a valid token requested for a configuration from the same application/environment pair to retrieve a second configuration, the operation will succeed but return an empty response. This is a little confusing, but I'm sure there are reasons as for why the service was implemented this way although I'll refrain from commenting on it since I don't know much about it. However, for the purpose of our use case, this means that we need a new token for each set of I came up with an implementation that would allow this before seeing your diagram, however I'm open to discuss and converge on either.
class AppConfigProvider extends BaseProvider {
public client: AppConfigDataClient;
private application: string;
private environment: string;
++ private identifiersTokens: Map<string, string> = new Map(); // TODO: find a better name
private latestConfiguration: Uint8Array | undefined;
-- private token: string | undefined; then, same file, but inside the ++ if (!this.identifiersTokens.has(name)) {
-- if (!this.token) {
...
-- this.token = session.InitialConfigurationToken;
++ if (!session.InitialConfigurationToken) throw new Error('Unable to retrieve the configuration token');
++ this.identifiersTokens.set(name, session.InitialConfigurationToken);
...
const getConfigurationCommand = new GetLatestConfigurationCommand({
-- ConfigurationToken: this.token,
++ ConfigurationToken: this.identifiersTokens.get(name),
});
...
-- this.token = response.NextPollConfigurationToken;
++ if (response.NextPollConfigurationToken) {
++ this.identifiersTokens.set(name, response.NextPollConfigurationToken);
++ } else {
++ // This will force a new session to be created in the next execution
++ this.identifiersTokens.delete(name);
++ } See full method implementation below: protected async _get(
name: string,
options?: AppConfigGetOptionsInterface
): Promise<Uint8Array | undefined> {
/**
* The new AppConfig APIs require two API calls to return the configuration
* First we start the session and after that we retrieve the configuration
* We need to store the token to use in the next execution
**/
if (!this.identifiersTokens.has(name)) {
const sessionOptions: StartConfigurationSessionCommandInput = {
ConfigurationProfileIdentifier: name,
EnvironmentIdentifier: this.environment,
ApplicationIdentifier: this.application,
};
if (options?.sdkOptions) {
Object.assign(sessionOptions, options.sdkOptions);
}
const sessionCommand = new StartConfigurationSessionCommand(
sessionOptions
);
const session = await this.client.send(sessionCommand);
if (!session.InitialConfigurationToken) throw new Error('Unable to retrieve the configuration token');
this.identifiersTokens.set(name, session.InitialConfigurationToken);
}
const getConfigurationCommand = new GetLatestConfigurationCommand({
ConfigurationToken: this.identifiersTokens.get(name),
});
const response = await this.client.send(getConfigurationCommand);
if (response.NextPollConfigurationToken) {
this.identifiersTokens.set(name, response.NextPollConfigurationToken);
} else {
// This will force a new session to be created in the next execution
this.identifiersTokens.delete(name);
}
const configuration = response.Configuration;
if (configuration) {
this.latestConfiguration = configuration;
}
return this.latestConfiguration;
} This logic is pretty similar to the one in your diagram, however it would allow us to handle & cache the tokens of multiple configurations at the same time. What do you think? |
I hope that my comments clarify all the open points we had. I realise that the communication/conversation is a bit all over the place due to the distributed nature of the comments, so if there's any point that we should discuss further, or that I have missed please don't hesitate to let me know. To sum up:
|
I like it, I mentioned something like that in the python issue. But I see a potential problem with the latest configuration. Quote from the issue:
Let's use your example:
We got the configuration for
Now the map has two tokens and this.latestConfiguration = "latest TestFreeForm configuration".
We hit the cache and used the token for
But
I think it is intended to reduce end-user costs because AWS charges for each API call and received configuration
If this potential problem with the latest configuration is real, seems like we should store configurations as well as tokens. Or flush the token if it was called with a different What do you think? |
Sorry for the lengthy discussion around that. Thanks for the help and clarification. I appreciate that. I will adjust the implementation and write more tests for different use cases while we decide on the caching tokens. |
No, this is a great discussion and it's worth having it I think. I realised that I might not have been very clear in my previous message, so I have recorded a short screen share where I explain my implementation idea & also show a debugger session to explain what happens at each point, I hope it helps: 2022-12-30.16-10-42.mp4In regards to why this is needed, you are correct, the reason is mainly cost savings but also performances (which in the case of Lambda almost directly translates to additional cost savings since shorter execution = lower cost). Adding the storage of the tokens would be a second layer of caching. The first one is the storage of the configurations, which as you pointed out, is needed and we are in fact doing. If you check the implementation of Given this first level of caching, we can assume that a number of retrievals will be covered by the main cache. However the function's environment lasts longer and the cache expires, or the user wants to fetch an updated version (via the |
After our conversation on Discord I'd like to amend part of my understanding of how the AppConfig service works and acknowledge that you were correct in your understanding. Looking at this section of the docs (which I think you were referring to), there's this statement:
This means that regardless of our implementation / caching, if users want to call the API they need to be ready to handle empty responses in their code if the config hasn't changed in the meanwhile. Additionally, there's another case that we haven't mentioned, but that doesn't affect the implementation: the response returns a In the video I posted above, I mistakenly assumed that the last config was retrieved from the service, while in fact it must have been retrieved from the cache since it should have been empty. I was able to confirm this by re-running the same sample this time using |
184d4b3
to
2e4bb76
Compare
Hey @dreamorosi! Some comments for the PR. If you have any questions, I'm happy to answer. As I get it from our conversation, a user should handle the cases below:
So I return it as-is, so all the errors will be caught in For the utility function, I combined interfaces in Future thoughts for the next cycles:
|
Correct.
I understand why this is necessary, however I'm on the fence about this. My main question mark is on whether we should restrict the type to avoid passing interface getAppConfigCombinedInterface
extends Omit<AppConfigProviderOptions, 'clientConfig>,
AppConfigGetOptionsInterface {} The reason I suggest this is for coherence with the other providers. None of the other providers allows to customize the AWS SDK when using the utility functions. That should be a behavior (and a reason to use) for the class providers.
That's an interesting proposal, I hadn't thought about that and I can see the value. I am however concerned about implicitly overloading the meaning of the On the other hand, if someone wanted to have a break-glass solution to forcefully fetch the value no matter the cost / timeout values, there's no way. I think it's worth discussing this, but I'd like to do so across all versions of Powertools, not just us.
Very true, I've been thinking about this a lot as well and I don't love it. However, given that (like the point above) it's a new behavior/feature, I'd like to discuss this with the larger Powertools ecosystem. I'm in the process of writing a RFC which I'll post at a later time, but my current stance is that this is not something that should be there in v1 of the Parameters utility and could come afterwards. Basically I'd like to focus our limited resources/cycles on laying out solid foundations and then having people start using the library before adding/perfecting features. Hope it makes sense. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking great, I have one last comment/point about the getAppConfigCombinedInterface
interface that I'd like to hash out.
After that I'm looking forward to merge this asap!
Sure. I just want to let you know and save my thoughts for future discussions. |
Description of your changes
Related issue #1177
How to verify this change
Related issues, RFCs
Issue number: #1177
PR status
Is this ready for review?: YES
Is it a breaking change?: NO
Checklist
Breaking change checklist
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.