-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Eliminated GrainState #1060
Eliminated GrainState #1060
Conversation
Great work! I was just starting to implement this myself! Please see the comments as well as: #1035 (comment). Thank you for this :) |
Great work! Impressive how an issue that we was discussing in less than a week was already implemented! Keep pushing @dVakulen ! 😄 |
4831d36
to
1213d61
Compare
This is big. Thank you for taking on it, @dVakulen! I'll take a closer look at it and try to test it tomorrow. I know @johnazariah will be very happy. :-) |
Updated accordingly to the #1035 (comment) |
/// Base class for generated grain state classes. | ||
/// </summary> | ||
[Serializable] | ||
public abstract class GrainState |
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.
If backward compatibility is needed, GrainState
can be marked as deprecated.
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.
Yes, let's mark it deprecated and leave for a release or two.
I totally missed this PR :) |
@shayhatsor I like the idea of Provider remains stateless, no assumptions on what information the provider needs to associate with the grain. We could have them specify a What are the drawbacks? |
@ReubenBond I think I get what you're saying. This is what seems logical to me at this point:
In conclusion: public interface IGrainState
{
object State { get; set; }
}
public class GrainState<T> : IGrainState
{
public T State { get; set; }
object IGrainState.State
{
get { return State; }
set { State = (T) value; }
}
}
public class Grain<T> : Grain
{
protected Grain()
{
GrainState = new GrainState<T>();
}
protected Grain(IGrainIdentity identity, IGrainRuntime runtime, GrainState<T> state, IStorage storage)
: base(identity, runtime)
{
GrainState = state;
Storage = storage;
}
protected T State
{
get { return (T) GrainState.State; }
set { GrainState.State = value; }
}
protected virtual Task ClearStateAsync()
{
return Storage.ClearStateAsync();
}
protected virtual Task WriteStateAsync()
{
return Storage.WriteStateAsync();
}
protected virtual Task ReadStateAsync()
{
return Storage.ReadStateAsync();
}
}
public interface IStorageProvider : IProvider
{
Logger Log { get; }
Task ReadStateAsync(Type grainType, GrainReference grainReference, IGrainState grainState);
Task WriteStateAsync(Type grainType, GrainReference grainReference, IGrainState grainState);
Task ClearStateAsync(Type grainType, GrainReference grainReference, IGrainState grainState);
} |
I believe we can improve the storage model by raising the abstraction a little higher. The grain actually talks to an IStorage class rather then the storage provider.
This class acts as a bridge between the grain and the storage provider. The storage provider interfaces changes are a change to the contract with this class, not the grain. Instead of creating a provider per grain, I propose we request the IStorage object from the provider instead.
During grain construction we can obtain the storage object from the storage provider, then use it to store the state. Because we've raised the abstraction layer (to the IStorage interface), older storage providers can still be maintained with the current IStorage implementation (GrainStateStorageBridge), and future providers can implement any type of state to storage translation they like. This also allows storage providers to take advantage of per grain storage constructs for things like caching layers and the like. |
Getting the IStorage implementation from the storage provider also affords us a natural extension point for more specialized storage patterns. Consider an event sourcing storage provider something like the following:
This storage provider could be used as a regular storage provider, while allowing grains using event sourcing to use the more advanced features. This is a general argument, I've know idea if those calls make sense for event sourcing :) |
@jason-bragg sounds good to me. I was trying to say pretty much that:
I support your approach 👍 |
In order to make the interface as clean as possible, I believe we shouldn't pass public interface IGrainIdentity
{
Guid GuidKey { get; }
Nullable<long> LongKey { get; }
string StringKey { get; }
string Identity { get; }
} so the interface can be: public interface IStorageProvider : IProvider
{
IStorage GetStorage(Type grainType, IGrainIdentity grainIdentity, IGrainState grainState);
} what do you think @jason-bragg and @ReubenBond ? |
If I understand correctly, in Orleans, a grain reference is a grain's identifier. Creating a new id type would be redundant. Can you be more specific regarding your concerns about grain references? @gabikliot and @sergeybykov are more versed on the intended usage of grain references. |
@jason-bragg, there's already an public interface IGrainIdentity
{
Guid PrimaryKey { get; }
long PrimaryKeyLong { get; }
string PrimaryKeyString { get; }
string IdentityString { get; }
long GetPrimaryKeyLong(out string keyExt);
Guid GetPrimaryKey(out string keyExt);
} to public interface IGrainIdentity
{
Guid GuidKey { get; }
Nullable<long> LongKey { get; }
string StringKey { get; }
string Identity { get; }
} so the |
+1 to @jason-bragg suggested abstraction. I'm not very fond of the very generic |
BTW, regarding naming, I didn't realize that |
|
@sergeybykov, I know, that's why I asked to add the real grain type. I don't see how grain type code is useful outside of Orleans. |
I believe I addressed the serializer generation issue with #1211. |
LGTM on my side. |
I believe we only partially addressed the serializer generation in #1211 - couldn't a user have a |
@ReubenBond The check I added is for all classes that extend |
@sergeybykov if the client calls: EDIT: that's not a blocking issue, it's not an issue introduced by this change, and users can always use |
Or by marking |
// Null out the in-memory copy of the state | ||
grain.GrainState.SetAll(null); | ||
grain.GrainState.State = null; |
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.
I think this is a bug - instead of nulling State
we need to set it to a fresh instance of the state class.
Grain_AzureStore_Delete
test is failing because of this. It tries to read a value of a state property right after calling ClearStateAsync
, and the grain code throws a NullReferenceException
.
@ReubenBond In general, our generation of serializers is a best effort attempt. We can never guarantee that the app won't pass an unseralizable object, a subclass or something. So I agree, this is not really an issue with this PR, just a general limit to our serialization 'magic'. |
|
bce5efb
to
4feea24
Compare
4feea24
to
4827d91
Compare
Fixed. |
This version pass all tests. So I'll merge it. @dVakulen Thank you so much for the important contribution and the patience to get it in! |
Weeee! Thanks @dVakulen! Great work! :) 👏 |
Thanks @dVakulen ! |
@dVakulen My thanks too. 👏 |
Thanks for your, @sergeybykov, @gabikliot, @ReubenBond, patience and reviews, and all the other participants for ideas. |
#1035.
Implemented POCO state, added etag to the SP domain, available for the further discussion.
The API of IStorageProvider changed to support writes with eTag.