Skip to content
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

Access full component metadata in ServiceBuilder.RegisterStateStore #37

Open
olitomlinson opened this issue Jul 25, 2023 · 2 comments
Open
Labels
kind/proposal A new feature or change is requested

Comments

@olitomlinson
Copy link

Describe the proposal

It would be really convenient if the context object (provided by ServiceBuilder.RegisterStateStore) actually contained all the component metadata on it, as well as just the instanceId.

I ask because in my implementation, at the point of registering my state store, I need the connection string from the metadata, in order to spin up a aspnet hosted BackgroundService which can create some prerequisite tables, that will later be depended on by the usual state store operators (sets, gets etc)

As it stands today, I have to wait until the IPluggableComponent.InitAsync() method is called to get access to the full metadata (MetadataRequest.Properties) so I can access the connection string and then pass it back via a TaskCompletionSource<string> to the BackgroundService. It works, but leads to a messy abstraction.

If you’re with me so far, and agree that making the component metadata available at the point of ServiceBuilder.RegisterStateStore makes sense, then I could suggest we go a step further…. which would make the IPluggableComponent.InitAsync() method completely redundant and could be removed from the API surface.


This is my current implementation :

image
image

But I would like to see something like this :

image
image

@olitomlinson olitomlinson added the kind/proposal A new feature or change is requested label Jul 25, 2023
@olitomlinson
Copy link
Author

@philliphoff has provided a starting point

internal sealed class DeferredStateStore<T> : IStateStore
    where T : IStateStore
{
    private readonly Func<MetadataRequest?, T> componentFactory;
    private readonly ILogger<DeferredStateStore<T>> logger;
    private readonly Lazy<T> stateStore;

    private MetadataRequest? metadataRequest;

    public DeferredStateStore(Func<MetadataRequest?, T> componentFactory, ILogger<DeferredStateStore<T>> logger)
    {
        this.componentFactory = componentFactory;
        this.logger = logger;

        this.stateStore = new Lazy<T>(() => this.componentFactory(this.metadataRequest));

        this.logger.LogInformation("DeferredStateStore created");
    }

    #region IStateStore Members

    public Task DeleteAsync(StateStoreDeleteRequest request, CancellationToken cancellationToken = default)
    {
        return this.stateStore.Value.DeleteAsync(request, cancellationToken);
    }

    public Task<StateStoreGetResponse?> GetAsync(StateStoreGetRequest request, CancellationToken cancellationToken = default)
    {
        return this.stateStore.Value.GetAsync(request, cancellationToken);
    }

    public Task InitAsync(MetadataRequest request, CancellationToken cancellationToken = default)
    {
        this.logger.LogInformation("DeferredStateStore initialized");

        this.metadataRequest = request;

        return Task.CompletedTask;
    }

    public Task SetAsync(StateStoreSetRequest request, CancellationToken cancellationToken = default)
    {
        return this.stateStore.Value.SetAsync(request, cancellationToken);
    }

    #endregion
}
serviceBuilder.RegisterStateStore(
            context =>
            {
                Console.WriteLine("Creating state store for instance '{0}' on socket '{1}'...", context.InstanceId, context.SocketPath);

                return new DeferredStateStore<MemoryStateStore>(
                    metadata =>
                    {
                        // TODO: Do whatever you need to do with the metadata here.

                        return new MemoryStateStore(context.ServiceProvider.GetRequiredService<ILogger<MemoryStateStore>>());
                    },
                    context.ServiceProvider.GetRequiredService<ILogger<DeferredStateStore<MemoryStateStore>>>());
            });

@philliphoff
Copy link
Collaborator

philliphoff commented Jul 25, 2023

As mentioned in a related discussion on the Dapr Discord, the creation of the store/pub-sub/binding instances is deferred until the first call (i.e. typically Init()). I considered deferring further until after initialization, but was concerned that "first call is initialization" is really just a convention and may not always be true in the future (e.g. given other features/ping calls). Whether that concern is warranted is probably debateable, but I also didn't want to stray too far from the lifecycle of other SDKs and "native" component development.

That's not to say that it's an unreasonable request, but it may be best to wait to see how many people require this pattern before deciding whether to fold the workaround (proxy with deferred creation) into the SDK.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/proposal A new feature or change is requested
Projects
None yet
Development

No branches or pull requests

2 participants