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

Commit 2392f64

Browse files
authored
Merge pull request #8 from wintoncode/generic-types-not-methods
Internalised implementations, provided factories, and moved generics …
2 parents e27f1f9 + 4dbd70e commit 2392f64

15 files changed

Lines changed: 425 additions & 254 deletions

README.md

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,33 @@ A facade library useful for [Entity](https://github.com/wintoncode/Winton.Domain
99

1010
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.
1111

12-
## Types
12+
## Facade Types
1313

14-
### IEntityFacade
15-
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.
17-
18-
### EntityFacade
14+
Note that the default implementations are 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.
1915

20-
The default implementation of `IEntityFacade`. The Create method supports automatic ID generation for string-serializable ID types, otherwise IDs must be set before creating.
16+
### IEntityFacade
2117

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.
18+
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. The Create method supports automatic ID generation for string-serializable ID types, otherwise IDs must be set before creating.
2319

2420
### IValueObjectFacade
2521

2622
An abstraction layer over Value Object operations in DocumentDb. Provides strong typed Create, Delete, and Query methods.
2723

28-
### ValueObjectFacade
29-
30-
The default implementation of `IValueObjectFacade`.
31-
32-
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.
33-
3424
## Usage
3525

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
26+
The default implementations of both `IEntityFacade` and `IValueObjectFacade` should be created from their provided factories. These can each be constructed from an [IDocumentClient](https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.documents.idocumentclient). Their Create methods both take a [Database](https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.documents.database) and a [DocumentCollection](https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.documents.documentcollection). Of course, resolving all dependencies from a DI container is preferred, but for clarity they can be manually constructed as
3727

3828
```csharp
29+
IDocumentClient documentClient = new DocumentClient(...);
30+
3931
Database database = new Database { Id = "AllTheData" };
4032
DocumentCollection documentCollection = new DocumentCollection { Id = "AccountingData" };
41-
IDocumentClient documentClient = new DocumentClient(...);
4233

43-
IEntityFacade entityFacade = new EntityFacade(database, documentCollection, documentClient);
44-
IValueObjectFacade valueObjectFacade = new ValueObjectFacade(database, documentCollection, documentClient);
34+
IEntityFacadeFactory entityFacadeFactory = new EntityFacadeFactory(documentClient);
35+
IValueObjectFacadeFactory valueObjectFacadeFactory = new ValueObjectFacadeFactory(documentClient);
36+
37+
IEntityFacade<Account, AccountId> entityFacade = entityFacadeFactory.Create<Account, AccountId>(database, documentCollection);
38+
IValueObjectFacade<AccountType> valueObjectFacade = valueObjectFacadeFactory.Create<AccountType>(database, documentCollection);
4539
```
4640

4741
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.
@@ -155,43 +149,43 @@ The respective implementations of these repositories, potentially defined in a s
155149
```csharp
156150
internal sealed class AccountRepository : IAccountRepository
157151
{
158-
private readonly IEntityFacade _entityFacade;
152+
private readonly IEntityFacade<Account, AccountId> _entityFacade;
159153

160-
public AccountRepository(IEntityFacade entityFacade)
154+
public AccountRepository(IEntityFacade<Account, AccountId> entityFacade)
161155
{
162156
_entityFacade = entityFacade;
163157
}
164158

165159
public async Task<AccountId> Create(Account account)
166160
{
167-
return (await _entityFacade.Create<Account, AccountId>(account)).Id;
161+
return (await _entityFacade.Create(account)).Id;
168162
}
169163

170164
public async Task<Account> Get(AccountId id)
171165
{
172-
return await _entityFacade.Read<Account, AccountId>(id);
166+
return await _entityFacade.Read(id);
173167
}
174168

175169
...
176170
}
177171

178172
internal sealed class TransactionRepository : ITransactionRepository
179173
{
180-
private readonly IEntityFacade _entityFacade;
174+
private readonly IEntityFacade<Transaction, TransactionId> _entityFacade;
181175

182-
public TransactionRepository(IEntityFacade entityFacade)
176+
public TransactionRepository(IEntityFacade<Transaction, TransactionId> entityFacade)
183177
{
184178
_entityFacade = entityFacade;
185179
}
186180

187181
public async Task<TransactionId> Create(Transaction transaction)
188182
{
189-
return (await _entityFacade.Create<Transaction, TransactionId>(transaction)).Id;
183+
return (await _entityFacade.Create(transaction)).Id;
190184
}
191185

192186
public IEnumerable<Transaction> GetAllSentBy(AccountId accountId)
193187
{
194-
return _entityFacade.Query<Transaction, TransactionId>()
188+
return _entityFacade.Query()
195189
.Where(t => t.Sender == accountId)
196190
.AsEnumerable();
197191
}
@@ -201,21 +195,21 @@ internal sealed class TransactionRepository : ITransactionRepository
201195

202196
internal sealed class AccountTypeRepository : IAccountTypeRepository
203197
{
204-
private readonly IValueObjectFacade _valueObjectFacade;
198+
private readonly IValueObjectFacade<AccountType> _valueObjectFacade;
205199

206-
public AccountTypeRepository(IValueObjectFacade valueObjectFacade)
200+
public AccountTypeRepository(IValueObjectFacade<AccountType> valueObjectFacade)
207201
{
208202
_valueObjectFacade = valueObjectFacade;
209203
}
210204

211205
public async Task Create(AccountType accountType)
212206
{
213-
await _valueObjectFacade.Create<AccountType>(accountType);
207+
await _valueObjectFacade.Create(accountType);
214208
}
215209

216210
public IEnumerable<AccountType> GetAll()
217211
{
218-
return _valueObjectFacade.Query<AccountType>()
212+
return _valueObjectFacade.Query()
219213
.AsEnumerable();
220214
}
221215

src/Winton.DomainModelling.DocumentDb/EntityFacade.cs

Lines changed: 11 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,14 @@
1010

1111
namespace Winton.DomainModelling.DocumentDb
1212
{
13-
/// <inheritdoc />
14-
/// <summary>
15-
/// An abstraction layer over <see cref="Entity{TEntityId}" /> CRUD operations in DocumentDb. Allows multiple types to
16-
/// be transparently stored in one collection using a 'wrapper' document type with a type discriminator and namespaced
17-
/// ID.
18-
/// </summary>
19-
public sealed class EntityFacade : IEntityFacade
13+
internal sealed class EntityFacade<TEntity, TEntityId> : IEntityFacade<TEntity, TEntityId>
14+
where TEntity : Entity<TEntityId>
15+
where TEntityId : IEquatable<TEntityId>
2016
{
2117
private readonly Database _database;
2218
private readonly IDocumentClient _documentClient;
2319
private readonly DocumentCollection _documentCollection;
2420

25-
/// <summary>
26-
/// Initializes a new instance of the <see cref="EntityFacade" /> class.
27-
/// </summary>
28-
/// <param name="database">The DocumentDb database.</param>
29-
/// <param name="documentCollection">The DocumentDb collection. Partitioned collections are not supported.</param>
30-
/// <param name="documentClient">A document client implementation.</param>
3121
public EntityFacade(Database database, DocumentCollection documentCollection, IDocumentClient documentClient)
3222
{
3323
if (documentCollection.PartitionKey.Paths.Any())
@@ -40,18 +30,7 @@ public EntityFacade(Database database, DocumentCollection documentCollection, ID
4030
_documentClient = documentClient;
4131
}
4232

43-
/// <inheritdoc />
44-
/// <summary>
45-
/// Create an <see cref="T:Winton.DomainModelling.Entity`1" /> of a specified type. Supports automatic ID generation
46-
/// for <see cref="T:System.String" />-serializable ID types, otherwise IDs must be set before creating.
47-
/// </summary>
48-
/// <typeparam name="TEntity">The type of the entity.</typeparam>
49-
/// <typeparam name="TEntityId">The ID type of the entity.</typeparam>
50-
/// <param name="entity">The <see cref="T:Winton.DomainModelling.Entity`1" /> to persist.</param>
51-
/// <returns>The created <see cref="T:Winton.DomainModelling.Entity`1" />.</returns>
52-
public async Task<TEntity> Create<TEntity, TEntityId>(TEntity entity)
53-
where TEntity : Entity<TEntityId>
54-
where TEntityId : IEquatable<TEntityId>
33+
public async Task<TEntity> Create(TEntity entity)
5534
{
5635
var document = new EntityDocument<TEntity, TEntityId>(entity.WithId<TEntity, TEntityId>());
5736

@@ -62,31 +41,12 @@ public async Task<TEntity> Create<TEntity, TEntityId>(TEntity entity)
6241
return responseDocument.Entity;
6342
}
6443

65-
/// <inheritdoc />
66-
/// <summary>
67-
/// Delete an <see cref="T:Winton.DomainModelling.Entity`1" /> of a specified type by ID.
68-
/// </summary>
69-
/// <typeparam name="TEntity">The type of the entity.</typeparam>
70-
/// <typeparam name="TEntityId">The ID type of the entity.</typeparam>
71-
/// <param name="id">The ID of the <see cref="T:Winton.DomainModelling.Entity`1" /> to delete.</param>
72-
/// <returns>A Task.</returns>
73-
public async Task Delete<TEntity, TEntityId>(TEntityId id)
74-
where TEntity : Entity<TEntityId>
75-
where TEntityId : IEquatable<TEntityId>
44+
public async Task Delete(TEntityId id)
7645
{
77-
await _documentClient.DeleteDocumentAsync(GetUri<TEntity, TEntityId>(id));
46+
await _documentClient.DeleteDocumentAsync(GetUri(id));
7847
}
7948

80-
/// <inheritdoc />
81-
/// <summary>
82-
/// Query <see cref="T:Winton.DomainModelling.Entity`1" /> instances of a specified type.
83-
/// </summary>
84-
/// <typeparam name="TEntity">The type of the entity.</typeparam>
85-
/// <typeparam name="TEntityId">The ID type of the entity.</typeparam>
86-
/// <returns>An <see cref="T:System.Linq.IQueryable`1" />.</returns>
87-
public IQueryable<TEntity> Query<TEntity, TEntityId>()
88-
where TEntity : Entity<TEntityId>
89-
where TEntityId : IEquatable<TEntityId>
49+
public IQueryable<TEntity> Query()
9050
{
9151
string entityType = EntityDocument<TEntity, TEntityId>.GetDocumentType();
9252

@@ -95,22 +55,11 @@ public IQueryable<TEntity> Query<TEntity, TEntityId>()
9555
.Select(x => x.Entity);
9656
}
9757

98-
/// <inheritdoc />
99-
/// <summary>
100-
/// Read an <see cref="T:Winton.DomainModelling.Entity`1" /> of a specified type by ID.
101-
/// </summary>
102-
/// <typeparam name="TEntity">The type of the entity.</typeparam>
103-
/// <typeparam name="TEntityId">The ID type of the entity.</typeparam>
104-
/// <param name="id">The ID of the <see cref="T:Winton.DomainModelling.Entity`1" /> to read.</param>
105-
/// <returns>The <see cref="T:Winton.DomainModelling.Entity`1" /> with the given ID, if it exists, otherwise null.</returns>
106-
public async Task<TEntity> Read<TEntity, TEntityId>(TEntityId id)
107-
where TEntity : Entity<TEntityId>
108-
where TEntityId : IEquatable<TEntityId>
58+
public async Task<TEntity> Read(TEntityId id)
10959
{
11060
try
11161
{
112-
ResourceResponse<Document> response =
113-
await _documentClient.ReadDocumentAsync(GetUri<TEntity, TEntityId>(id));
62+
ResourceResponse<Document> response = await _documentClient.ReadDocumentAsync(GetUri(id));
11463

11564
EntityDocument<TEntity, TEntityId> responseDocument = (dynamic)response.Resource;
11665

@@ -122,17 +71,7 @@ public async Task<TEntity> Read<TEntity, TEntityId>(TEntityId id)
12271
}
12372
}
12473

125-
/// <inheritdoc />
126-
/// <summary>
127-
/// Upsert an <see cref="T:Winton.DomainModelling.Entity`1" /> of a specified type. The ID must be set.
128-
/// </summary>
129-
/// <typeparam name="TEntity">The type of the entity.</typeparam>
130-
/// <typeparam name="TEntityId">The ID type of the entity.</typeparam>
131-
/// <param name="entity">The <see cref="T:Winton.DomainModelling.Entity`1" /> to upsert.</param>
132-
/// <returns>The upserted <see cref="T:Winton.DomainModelling.Entity`1" />.</returns>
133-
public async Task<TEntity> Upsert<TEntity, TEntityId>(TEntity entity)
134-
where TEntity : Entity<TEntityId>
135-
where TEntityId : IEquatable<TEntityId>
74+
public async Task<TEntity> Upsert(TEntity entity)
13675
{
13776
if (Equals(entity.Id, default(TEntityId)))
13877
{
@@ -153,9 +92,7 @@ private Uri GetUri()
15392
return UriFactory.CreateDocumentCollectionUri(_database.Id, _documentCollection.Id);
15493
}
15594

156-
private Uri GetUri<TEntity, TEntityId>(TEntityId id)
157-
where TEntity : Entity<TEntityId>
158-
where TEntityId : IEquatable<TEntityId>
95+
private Uri GetUri(TEntityId id)
15996
{
16097
return UriFactory.CreateDocumentUri(
16198
_database.Id,
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) Winton. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
using System;
5+
using Microsoft.Azure.Documents;
6+
7+
namespace Winton.DomainModelling.DocumentDb
8+
{
9+
/// <inheritdoc />
10+
/// <summary>
11+
/// The default factory to create an <see cref="IEntityFacade{TEntity,TEntityId}" /> for an
12+
/// <see cref="Entity{TEntityId}" /> of a specified type.
13+
/// </summary>
14+
public sealed class EntityFacadeFactory : IEntityFacadeFactory
15+
{
16+
private readonly IDocumentClient _documentClient;
17+
18+
/// <summary>
19+
/// Initializes a new instance of the <see cref="EntityFacadeFactory" /> class.
20+
/// </summary>
21+
/// <param name="documentClient">A document client implementation.</param>
22+
public EntityFacadeFactory(IDocumentClient documentClient)
23+
{
24+
_documentClient = documentClient;
25+
}
26+
27+
/// <inheritdoc />
28+
/// <summary>
29+
/// Create an <see cref="IEntityFacade{TEntity,TEntityId}" /> for an <see cref="Entity{TEntityId}" /> of a specified
30+
/// type. The default implementation allows multiple types to be transparently stored in one collection using a
31+
/// 'wrapper' document type with a type discriminator and namespaced ID.
32+
/// </summary>
33+
/// <typeparam name="TEntity">The type of the entity.</typeparam>
34+
/// <typeparam name="TEntityId">The ID type of the entity.</typeparam>
35+
/// <param name="database">The DocumentDb database.</param>
36+
/// <param name="documentCollection">The DocumentDb collection. Partitioned collections are not supported.</param>
37+
/// <returns>The created <see cref="IEntityFacade{TEntity,TEntityId}" />.</returns>
38+
public IEntityFacade<TEntity, TEntityId> Create<TEntity, TEntityId>(
39+
Database database,
40+
DocumentCollection documentCollection)
41+
where TEntity : Entity<TEntityId>
42+
where TEntityId : IEquatable<TEntityId>
43+
{
44+
return new EntityFacade<TEntity, TEntityId>(database, documentCollection, _documentClient);
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)