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

Commit cb74cd9

Browse files
committed
Added README
1 parent 1e6e5e1 commit cb74cd9

3 files changed

Lines changed: 164 additions & 4 deletions

File tree

README.md

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Winton.DomainModelling.DocumentDb
2+
3+
[![Build status](https://ci.appveyor.com/api/projects/status/9472sx6gw9y7lq0q?svg=true)](https://ci.appveyor.com/project/wintoncode/winton-domainmodelling-documentdb/branch/master)
4+
[![Travis Build Status](https://travis-ci.org/wintoncode/Winton.DomainModelling.DocumentDb.svg?branch=master)](https://travis-ci.org/wintoncode/Winton.DomainModelling.DocumentDb)
5+
[![NuGet version](https://img.shields.io/nuget/v/Winton.DomainModelling.DocumentDb.svg)](https://www.nuget.org/packages/Winton.DomainModelling.DocumentDb)
6+
[![NuGet version](https://img.shields.io/nuget/vpre/Winton.DomainModelling.DocumentDb.svg)](https://www.nuget.org/packages/Winton.DomainModelling.DocumentDb)
7+
8+
A facade library useful for [Entity](https://github.com/wintoncode/Winton.DomainModelling.Abstractions#entity) operations in DocumentDb (SQL API).
9+
10+
## Types
11+
12+
### IEntityFacade
13+
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.
15+
16+
### EntityFacade
17+
18+
The default implementation of `IEntityFacade`. The Create method supports automatic ID generation for string-serializable ID types, otherwise IDs must be set before creating.
19+
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.
21+
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+
## Usage
25+
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
27+
28+
```csharp
29+
Database database = new Database { Id = "AllTheData" };
30+
DocumentCollection documentCollection = new DocumentCollection { Id = "AccountingData" };
31+
32+
IDocumentClient documentClient = new DocumentClient(...);
33+
34+
IEntityFacade entityFacade = new EntityFacade(database, documentCollection, documentClient);
35+
```
36+
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.
38+
39+
```csharp
40+
[JsonConverter(typeof(SingleValueConverter))]
41+
public struct AccountId : IEquatable<AccountId>
42+
{
43+
...
44+
}
45+
46+
public sealed class Account : Entity<AccountId>
47+
{
48+
public Account(
49+
AccountId id,
50+
AccountName name,
51+
...)
52+
: base(id)
53+
{
54+
Name = name;
55+
...
56+
}
57+
58+
public AccountName Name { get; }
59+
60+
...
61+
}
62+
63+
[JsonConverter(typeof(SingleValueConverter))]
64+
public struct TransactionId : IEquatable<TransactionId>
65+
{
66+
...
67+
}
68+
69+
public sealed class Transaction : Entity<TransactionId>
70+
{
71+
public Transaction(
72+
TransactionId id,
73+
AccountId sender,
74+
AccountId recipient,
75+
...)
76+
: base(id)
77+
{
78+
Sender = sender;
79+
Recipient = recipient;
80+
...
81+
}
82+
83+
public AccountId Sender { get; }
84+
85+
public AccountId Recipient { get; }
86+
87+
...
88+
}
89+
```
90+
91+
These types could each have their own repository interfaces, defined within the "Accounting" domain.
92+
93+
```csharp
94+
public interface IAccountRepository
95+
{
96+
Task<AccountId> Create(Account account);
97+
98+
Task<Account> Get(AccountId id);
99+
100+
...
101+
}
102+
103+
public interface ITransactionRepository
104+
{
105+
Task<TransactionId> Create(Transaction transaction);
106+
107+
IEnumerable<Transaction> GetAllSentBy(AccountId accountId);
108+
109+
...
110+
}
111+
```
112+
113+
The respective implementations of these repositories, potentially defined in a separate "Persistence" layer, would simply be thin wrappers around the `IEntityFacade`.
114+
115+
```csharp
116+
internal sealed class AccountRepository : IAccountRepository
117+
{
118+
private readonly IEntityFacade _entityFacade;
119+
120+
public AccountRepository(IEntityFacade entityFacade)
121+
{
122+
_entityFacade = entityFacade;
123+
}
124+
125+
public async Task<AccountId> Create(Account account)
126+
{
127+
return (await _entityFacade.Create<Account, AccountId>(account)).Id;
128+
}
129+
130+
public async Task<Account> Get(AccountId id)
131+
{
132+
return await _entityFacade.Read<Account, AccountId>(id);
133+
}
134+
135+
...
136+
}
137+
138+
internal sealed class TransactionRepository : ITransactionRepository
139+
{
140+
private readonly IEntityFacade _entityFacade;
141+
142+
public TransactionRepository(IEntityFacade entityFacade)
143+
{
144+
_entityFacade = entityFacade;
145+
}
146+
147+
public async Task<TransactionId> Create(Transaction transaction)
148+
{
149+
return (await _entityFacade.Create<Transaction, TransactionId>(transaction)).Id;
150+
}
151+
152+
public IEnumerable<Transaction> GetAllSentBy(AccountId accountId)
153+
{
154+
return _entityFacade.Query<Transaction, TransactionId>()
155+
.Where(t => t.Sender == accountId);
156+
}
157+
158+
...
159+
}
160+
```
161+
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 immutable once entities have been persisted.

src/Winton.DomainModelling.DocumentDb/EntityFacade.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ public EntityFacade(Database database, DocumentCollection documentCollection, ID
4343
/// <inheritdoc />
4444
/// <summary>
4545
/// Create an <see cref="T:Winton.DomainModelling.Entity`1" /> of a specified type. Supports automatic ID generation
46-
/// for
47-
/// <see cref="T:System.String" />-serializable ID types, otherwise IDs must be set before creating.
46+
/// for <see cref="T:System.String" />-serializable ID types, otherwise IDs must be set before creating.
4847
/// </summary>
4948
/// <typeparam name="TEntity">The type of the entity.</typeparam>
5049
/// <typeparam name="TEntityId">The ID type of the entity.</typeparam>

src/Winton.DomainModelling.DocumentDb/IEntityFacade.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ namespace Winton.DomainModelling.DocumentDb
1313
public interface IEntityFacade
1414
{
1515
/// <summary>
16-
/// Create an <see cref="Entity{TEntityId}" /> of a specified type. Supports automatic ID generation for
17-
/// <see cref="string" />-serializable ID types, otherwise IDs must be set before creating.
16+
/// Create an <see cref="Entity{TEntityId}" /> of a specified type.
1817
/// </summary>
1918
/// <typeparam name="TEntity">The type of the entity.</typeparam>
2019
/// <typeparam name="TEntityId">The ID type of the entity.</typeparam>

0 commit comments

Comments
 (0)