OrleansContrib / Orleans.Providers.MongoDB

A MongoDb implementation of the Orleans Providers: Membership, Storage and Reminders.
MIT License
100 stars 43 forks source link

Expectations for Grain identity, state, and Mongo _id #92

Closed officeSpacePirate closed 2 years ago

officeSpacePirate commented 2 years ago

I am having trouble setting up the StorageProvider against an existing collection and could use some guidance. I am unable to get a simple UserGrain to load it's state from the database, but I believe I have everything setup as expected.

Going through the Test project here, I noticed that the only Grain used is WithIntKey and an Id property is not specified on the state object. What are the expectations around using native Mongo ObjectId's and adding them to state object? I am using [BsonId] & [BsonRepresentation(BsonType.ObjectId)] from the driver like one would expect when maintaining their own repository layer; is this incompatible with the StorageProvider and Grain PrimaryKey?

Here are some snippets from my prototype:

[BsonIgnoreExtraElements(Inherited = true)]
public abstract class Entity
{
  [BsonId]
  [BsonRepresentation(BsonType.ObjectId)]
  public virtual string Id { get; set; }
}
public class UserState : Entity
{
  public string Email { get; set; }
  public string DisplayName { get; set; }
  public DateTime DateActive { get; set; } = DateTime.UtcNow;
}
[StorageProvider(ProviderName = "MongoDBStore")]
public class UserGrain: Grain<UserState>, IUserGrain
{
  public async Task<UserState> GetUserAsync()
  {
    var user = await Task.FromResult(State);
    return user;
  }
}

I have another Grain setup as a StatelessWorker with repository access for the same collection/types that I wrote that gets a list of users just fine. When I use one of those Ids to instantiate the Grain using PersistentState, it always loads a null object. Perhaps things aren't meant to load automatically like I think they do? Any help would be much appreciated.

officeSpacePirate commented 2 years ago

Just created a simple test to write documents to the database and realize what the issue is. Since the StorageProvider is setup to write grains in nested documents, does that mean this is not compatible with existing databases/collections? I'm trying this out with a prototype project, but would like to move to PersistentState in a well established app.

This also makes me curious whether this structure will lead to a performance hit for search indexes; e.g. I have a Grain to manage UserState, but also have a stateless service to search Users from the same collection. Seems like this may or may not necessitate a separate view for searching.

Just in case anyone has similar issues getting started with this for StorageProvider, here's the expected structure for the data object I specified above:

{
  "_id": "GrainReference={giant number here, looks like an auto-generated long}",
  "_etag": "{Guid}",
  "_doc": {
    "__id": "1",  // this seems to be generated
    "__type": "{State object type}, {project}",
    "Email": "string",
    "DisplayName": "string",
    "DateActive": "2021-11-24T17:44:40Z",
    "Id": null  // notice this was added rather than being mapped because of the nested/extra '_'
  }
}

Once I understand the expectations better, maybe I can help contribute some docs? This probably seems super basic for those working on the project, but it would've saved me some time to know the StorageProvider's opinions 😄

SebastianStehle commented 2 years ago

Once I understand the expectations better, maybe I can help contribute some docs? This probably seems super basic for those working on the project, but it would've saved me some time to know the StorageProvider's opinions 😄

No, we don't your contributions, especially not for something as boring as documentation, where nobody wants to do something ;) ;)

diegofrata commented 2 years ago

@officeSpacePirate the provider uses Newtonsoft Json to serialize the state, not MongoDB's Bson serializer. So there's no point in tagging or using Bson attributes in your state, it won't be used. I recommend reading the source code, it's not long, will give you a good insight into how it works.

officeSpacePirate commented 2 years ago

Yeah, I've been doing a combination of that and playing with different options in a prototype project. I think I was naïve in my understanding of virtual actors and state management before. While streams, membership, etc. are really nice features to have from this lib, I'm considering removing PersistentState features from my application entirely and just building my own version of the features I want. It seems to me that Orleans PersistentState is something highly beneficial for relational databases, but very niche when using something like Mongo.

I am building a RESTful API service that has a few resources where having state in memory would be ideal, but none of my data needs to be in separate collections for the same API resource and most of my resources need to be listed or searched on. This makes PersistentState somewhat of a nuisance because I have to keep some data in sync between my collections and state management or combine data from two sources which seems unnecessary. I may come back to it and use Orleans streams or Mongo changestreams to keep the two eventually consistent, but I can keep things in memory on Grains in several simple ways without the overhead for now.

Thanks for the responses.