Skip to content

task: improve docs for custom persistence options #7

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

Closed
wants to merge 2 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dotnet add package JsonApiDotNetCore.MongoDb
### Models

```cs
// MongoDbIdentifiable is just a utility base class, could use IIdentifiable<TId> instead
public sealed class Book : MongoDbIdentifiable
{
[Attr]
Expand Down Expand Up @@ -63,14 +64,16 @@ public class Startup
}
}
```

Note: If your API project uses only MongoDB (not in combination with EF Core), then instead of
registering all MongoDB resources and repositories individually, you can use:

```cs
public class Startup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// ...
// ...

services.AddJsonApi(facade => facade.AddCurrentAssembly());
services.AddJsonApiMongoDb();
Expand All @@ -85,6 +88,60 @@ public class Startup
}
```

### Customise MongoDB persistence options and _id generation

In addition to `MongoDbIdentifiable` your resource classes are free to use any of the MongoDB driver persistence options or inherit from their own base class.

For example, you could change the example above so that the `Book` resource has string IDs rather than object ids in the DB, but still have them generated server side:

```cs
public class Book : IIdentifiable<string>
{
// If Id=null generate a random string ID using the MongoDB driver
[BsonId(IdGenerator = typeof(StringObjectIdGenerator))]
Copy link
Contributor

@bart-degreed bart-degreed Feb 16, 2021

Choose a reason for hiding this comment

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

I'm open to consider changing MongoDbIdentifiable to use this instead of [BsonId] [BsonRepresentation(BsonType.ObjectId)] if @mrnkr agrees this is better. In any case, The MongoDB fluent mapping API can overrule these using AutoMap, making it merely just a default for convenience.

Copy link
Author

Choose a reason for hiding this comment

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

The default of BsonType.ObjectId may be sensible as it is the most performant in MongoDB because it is the binary representation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good to know, then I think we should leave that the default.

[Attr]
public virtual string Id { get; set; }

// override the attribute name in the db
[BsonElement("bookName")]
[Attr]
public string Name { get; set; }

// all json:api resources need this
[BsonIgnore]
public string StringId { get => Id; set => Id = value; }
}
```

Resources just need to inherit from the base `IIdentifiable<string>` interface from JsonApiDotNetCore (or the provided default `MongoDbIdentifiable`) and then just use any of usual [MongoDB Driver mapping code](https://mongodb.github.io/mongo-csharp-driver/2.12/reference/bson/mapping/).

You could also achieve the exact same result using MongoDB `BsonClassMap` [rather than attributes](https://mongodb.github.io/mongo-csharp-driver/2.11/reference/bson/mapping/) so your `Book` does not need any MongoDB specific code like below.

```cs
// in startup
BsonClassMap.RegisterClassMap<Book>(cm =>
{
cm.AutoMap();
cm.MapIdProperty(x => x.Id).SetIdGenerator(StringObjectIdGenerator.Instance);
cm.UnmapMember(x=>x.StringId);
});
```

Using `StringObjectIdGenerator` above could then be combined with `AllowClientGeneratedIds` JsonApi setting in `Startup.ConfigureServices` so that IDs can be generated on the client, and will be auto-assigned server side if not provided providing a flexible string based id for the `Book` resource:

```cs
services.AddJsonApi(options => {
// Allow us to POST books with already assigned IDs!
options.AllowClientGeneratedIds = true;
}, resources: builder =>
{
builder.Add<Book, string>();
});
services.AddJsonApiMongoDb();

services.AddResourceRepository<MongoDbRepository<Book, string>>();
```

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is basic JsonApiDotNetCore functionality, which is already documented at https://www.jsonapi.net/usage/options.html#client-generated-ids. I don't see why it needs to be mentioned here, or am I missing something that makes this work differently for MongoDB that requires explanation? Note we have MongoDB-tests for this and all seem to work fine without any special handling.

Copy link
Author

Choose a reason for hiding this comment

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

The combination of mongoDB features (string ids generated server side) and json api features (optional client side id generation) working in tandem was hard to discover for me and took me a few passes to figure out which was why I was offering some docs up to save others the same hassle but accept what you say.

I think it is MongoDB specific because the library is defaulted to the (performant) binary object id. If you are trying to do client side id generation with AllowClientGeneratedIds then users (me!) may assume they can pass any string id since that is how the API appears. However, in reality the API requires you to pass a 12 byte unique generated object id, and throws an non-obvious exception if you just pass something like "123".

Using mongoDB StringObjectIdGenerator and json:api AllowClientGeneratedIds are a nice fit together in the document db ecosystem because it is common to have client-assigned document random string keys as the default that can be overridden with a unique string if I know the id I want to assign. This differs from relational SQL ecosystem where auto-increment database-assigned ids are the default, and client side key assignment is the exception.

Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense. But then we should have tests backing that scenario, if we're going to advise on doing that. Can you add some? They would demonstrate using the fluent mapping overrides too, which is nice.

## Development

Restore all NuGet packages with:
Expand Down