Skip to content
This repository was archived by the owner on Jan 27, 2022. It is now read-only.

Commit 734edcb

Browse files
iscottb122Iain Scott
authored andcommitted
Updated README.
1 parent cc88d94 commit 734edcb

1 file changed

Lines changed: 76 additions & 12 deletions

File tree

README.md

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,46 @@
55
[![NuGet version](https://img.shields.io/nuget/v/Winton.DomainModelling.DocumentDb.svg)](https://www.nuget.org/packages/Winton.DomainModelling.DocumentDb)
66
[![NuGet version](https://img.shields.io/nuget/vpre/Winton.DomainModelling.DocumentDb.svg)](https://www.nuget.org/packages/Winton.DomainModelling.DocumentDb)
77

8-
A facade library useful for [Entity](https://github.com/wintoncode/Winton.DomainModelling.Abstractions#entity) operations in DocumentDb (SQL API).
8+
A facade library useful for [Entity](https://github.com/wintoncode/Winton.DomainModelling.Abstractions#entity) and Value Object operations in DocumentDb (SQL API).
9+
10+
This implementations allow multiple types to be transparently stored in one collection using 'wrapper' documents with type discriminators and namespaced IDs (for entities). It can be tempting for those from a traditional SQL background to provision a separate collection per type. However, this is often unnecessarily expensive, especially if much of the reserved throughput for a given collection is unused. Taking advantage of the "schemaless" nature of a document store, such as DocumentDb, can both reduce cost and simplify infrastructural complexity. This implementation provides an easy way to work with a single collection within a bounded context (within which persisted type names are unique) while outwardly still achieving the desired level of strong typing. There really is a schema, but the database doesn't need to know about it.
911

1012
## Types
1113

1214
### IEntityFacade
1315

14-
An abstraction layer over [Entity](https://github.com/wintoncode/Winton.DomainModelling.Abstractions#entity) CRUD operations in DocumentDb. Provides strong typed Create, Read (including Query), **Upsert**, and Delete methods.
16+
An abstraction layer over [Entity](https://github.com/wintoncode/Winton.DomainModelling.Abstractions#entity) CRUD operations in DocumentDb. Provides strong typed Create, Read, **Upsert**, Delete, and Query methods.
1517

1618
### EntityFacade
1719

1820
The default implementation of `IEntityFacade`. The Create method supports automatic ID generation for string-serializable ID types, otherwise IDs must be set before creating.
1921

20-
This implementation allows multiple entity types to be transparently stored in one collection (using a 'wrapper' document with a type discriminator and namespaced ID). It can be tempting for those from a traditional SQL background to provision a separate collection per entity type. However, this is often unnecessarily expensive, especially if much of the reserved throughput for a given collection is unused. Taking advantage of the "schemaless" nature of a document store, such as DocumentDb, can both reduce cost and simplify infrastructural complexity. This implementation provides an easy way to work with a single collection within a bounded context (within which entity type names are unique) while outwardly still achieving the desired level of strong typing. There really is a schema, but the database doesn't need to know about it.
22+
Note that this implementation is currently **incompatible with partitioned collections**. This restriction could potentially be lifted in a future version, at the expense of implementation complexity (and probably a leakier abstraction). However, for applications requiring large collections, where partitioning is actually needed, the conveniences provided by this facade are unlikely to be suitable anyway.
23+
24+
### IValueObjectFacade
25+
26+
An abstraction layer over Value Object operations in DocumentDb. Provides strong typed Create, Delete, and Query methods.
27+
28+
### ValueObjectFacade
29+
30+
The default implementation of `IValueObjectFacade`.
2131

2232
Note that this implementation is currently **incompatible with partitioned collections**. This restriction could potentially be lifted in a future version, at the expense of implementation complexity (and probably a leakier abstraction). However, for applications requiring large collections, where partitioning is actually needed, the conveniences provided by this facade are unlikely to be suitable anyway.
2333

2434
## Usage
2535

26-
The constructor for `EntityFacade` takes a [Database](https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.documents.database), a [DocumentCollection](https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.documents.documentcollection), and an [IDocumentClient](https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.documents.idocumentclient). Of course, resolving all dependencies from a DI container is preferred, but for clarity an `EntityFacade` can be manually constructed as
36+
The constructors for both `EntityFacade` and `ValueObjectFacade` take a [Database](https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.documents.database), a [DocumentCollection](https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.documents.documentcollection), and an [IDocumentClient](https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.documents.idocumentclient). Of course, resolving all dependencies from a DI container is preferred, but for clarity they can be manually constructed as
2737

2838
```csharp
2939
Database database = new Database { Id = "AllTheData" };
3040
DocumentCollection documentCollection = new DocumentCollection { Id = "AccountingData" };
31-
3241
IDocumentClient documentClient = new DocumentClient(...);
3342

3443
IEntityFacade entityFacade = new EntityFacade(database, documentCollection, documentClient);
44+
IValueObjectFacade valueObjectFacade = new ValueObjectFacade(database, documentCollection, documentClient);
3545
```
3646

37-
Consider some application with an "Accounting" domain containing 2 entity types, `Account` and `Transaction`, each with a [strong typed](https://tech.winton.com/2017/06/strong-typing-a-pattern-for-more-robust-code/) ID. Note that both ID types use [SingleValueConverter](https://github.com/wintoncode/Winton.Extensions.Serialization.Json#singlevalueconverter), so they are string-serializable.
47+
Consider some application with an "Accounting" domain containing 2 entity types, `Account` and `Transaction`, each with a [strong typed](https://tech.winton.com/2017/06/strong-typing-a-pattern-for-more-robust-code/) ID. Note that both ID types use [SingleValueConverter](https://github.com/wintoncode/Winton.Extensions.Serialization.Json#singlevalueconverter), so they are string-serializable. The "Accounting" domain also contains a value object type, `AccountType`, used as reference data.
3848

3949
```csharp
4050
[JsonConverter(typeof(SingleValueConverter))]
@@ -47,15 +57,15 @@ public sealed class Account : Entity<AccountId>
4757
{
4858
public Account(
4959
AccountId id,
50-
AccountName name,
60+
AccountType type,
5161
...)
5262
: base(id)
5363
{
54-
Name = name;
64+
Type = type;
5565
...
5666
}
5767

58-
public AccountName Name { get; }
68+
public AccountType Type { get; }
5969

6070
...
6171
}
@@ -86,6 +96,27 @@ public sealed class Transaction : Entity<TransactionId>
8696

8797
...
8898
}
99+
100+
public struct AccountType : IEquatable<AccountType>
101+
{
102+
[JsonConstructor]
103+
public AccountType(
104+
string name,
105+
decimal rate,
106+
...)
107+
: base(id)
108+
{
109+
Name = name;
110+
Rate = rate;
111+
...
112+
}
113+
114+
public string Name { get; }
115+
116+
public decimal Rate { get; }
117+
118+
...
119+
}
89120
```
90121

91122
These types could each have their own repository interfaces, defined within the "Accounting" domain.
@@ -108,9 +139,18 @@ public interface ITransactionRepository
108139

109140
...
110141
}
142+
143+
public interface IAccountTypeRepository
144+
{
145+
Task Create(AccountType accountType);
146+
147+
IEnumerable<AccountType> GetAll();
148+
149+
...
150+
}
111151
```
112152

113-
The respective implementations of these repositories, potentially defined in a separate "Persistence" layer, would simply be thin wrappers around the `IEntityFacade`.
153+
The respective implementations of these repositories, potentially defined in a separate "Persistence" layer, would simply be thin wrappers around the `IEntityFacade` or `IValueObjectFacade`.
114154

115155
```csharp
116156
internal sealed class AccountRepository : IAccountRepository
@@ -152,11 +192,35 @@ internal sealed class TransactionRepository : ITransactionRepository
152192
public IEnumerable<Transaction> GetAllSentBy(AccountId accountId)
153193
{
154194
return _entityFacade.Query<Transaction, TransactionId>()
155-
.Where(t => t.Sender == accountId);
195+
.Where(t => t.Sender == accountId)
196+
.AsEnumerable();
197+
}
198+
199+
...
200+
}
201+
202+
internal sealed class AccountTypeRepository : IAccountTypeRepository
203+
{
204+
private readonly IValueObjectFacade _valueObjectFacade;
205+
206+
public AccountTypeRepository(IValueObjectFacade valueObjectFacade)
207+
{
208+
_valueObjectFacade = valueObjectFacade;
209+
}
210+
211+
public async Task Create(AccountType accountType)
212+
{
213+
await _valueObjectFacade.Create<AccountType>(accountType);
214+
}
215+
216+
public IEnumerable<AccountType> GetAll()
217+
{
218+
return _valueObjectFacade.Query<AccountType>()
219+
.AsEnumerable();
156220
}
157221

158222
...
159223
}
160224
```
161225

162-
These repositories will store their respective entities in a single shared collection, using the entity type names to namespace the IDs and discriminate between each type. Therefore, these names should be considered part of the document schema, and would therefore require a data migration if they were changed as part of a domain refactoring.
226+
These repositories will store their respective types in a single shared collection, using the type names to discriminate between each type and namespace the IDs (for entities). Therefore, these names should be considered part of the document schema, and would require a data migration if they were changed as part of a domain refactoring.

0 commit comments

Comments
 (0)