Skip to content

Commit abc9ff2

Browse files
authored
Merge pull request #32 from codepb/develop
Querying Improvements and Validation
2 parents ca3749a + cee11ce commit abc9ff2

33 files changed

+431
-60
lines changed

DDDToolkit.sln

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "DDDToolkit.Samples.Library.
3737
EndProject
3838
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DDDToolkit.Querying", "src\DDDToolkit.Querying\DDDToolkit.Querying.csproj", "{AAAF943F-C3DE-4BD5-BC2D-850E77A9B607}"
3939
EndProject
40-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DDDToolkit.Querying.Tests", "DDDToolkit.Querying.Tests\DDDToolkit.Querying.Tests.csproj", "{4D18A7F4-169C-4399-BFF3-F2F8AE665C55}"
40+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DDDToolkit.Querying.Tests", "tests\DDDToolkit.Querying.Tests\DDDToolkit.Querying.Tests.csproj", "{4D18A7F4-169C-4399-BFF3-F2F8AE665C55}"
41+
EndProject
42+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DDDToolkit.Validation", "src\DDDToolkit.Validation\DDDToolkit.Validation.csproj", "{0D0A57EF-3B95-4D6B-A3E6-0217FEB0DC6F}"
4143
EndProject
4244
Global
4345
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -103,6 +105,10 @@ Global
103105
{4D18A7F4-169C-4399-BFF3-F2F8AE665C55}.Debug|Any CPU.Build.0 = Debug|Any CPU
104106
{4D18A7F4-169C-4399-BFF3-F2F8AE665C55}.Release|Any CPU.ActiveCfg = Release|Any CPU
105107
{4D18A7F4-169C-4399-BFF3-F2F8AE665C55}.Release|Any CPU.Build.0 = Release|Any CPU
108+
{0D0A57EF-3B95-4D6B-A3E6-0217FEB0DC6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
109+
{0D0A57EF-3B95-4D6B-A3E6-0217FEB0DC6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
110+
{0D0A57EF-3B95-4D6B-A3E6-0217FEB0DC6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
111+
{0D0A57EF-3B95-4D6B-A3E6-0217FEB0DC6F}.Release|Any CPU.Build.0 = Release|Any CPU
106112
EndGlobalSection
107113
GlobalSection(SolutionProperties) = preSolution
108114
HideSolutionNode = FALSE
@@ -123,6 +129,7 @@ Global
123129
{1D41D8D2-B6A5-47DD-8E98-9512EC48B429} = {BDD881C3-118B-42D6-9D77-DDAB213CA4B0}
124130
{AAAF943F-C3DE-4BD5-BC2D-850E77A9B607} = {EF054C6D-056E-417A-A40A-376A20DF90A6}
125131
{4D18A7F4-169C-4399-BFF3-F2F8AE665C55} = {57967491-B4BB-44B9-B270-6533A1E388B0}
132+
{0D0A57EF-3B95-4D6B-A3E6-0217FEB0DC6F} = {EF054C6D-056E-417A-A40A-376A20DF90A6}
126133
EndGlobalSection
127134
GlobalSection(ExtensibilityGlobals) = postSolution
128135
SolutionGuid = {E3DCC906-DD65-4478-A617-AC943C719834}

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
# DDDToolkit
22
[<img src="https://ninthlight.visualstudio.com/_apis/public/build/definitions/2a0b2dd5-5fb3-41e4-a3bb-09ae80277b94/6/badge"/>](https://ninthlight.visualstudio.com/2a0b2dd5-5fb3-41e4-a3bb-09ae80277b94/_build/index?definitionId=6)
3+
4+
DDD Toolkit is a collection of libraries to support development following a Domain Driven Design pattern, using dotnet standard. The libraries are designed to support development by providing the building blocks for you to build on.
5+
6+
Find more in the [Wiki](https://github.com/codepb/DDDToolkit/wiki)

samples/library/DDDToolkit.Samples.Library.Domain/DDDToolkit.Samples.Library.Domain.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
<ItemGroup>
1212
<ProjectReference Include="..\..\..\src\DDDToolkit.Core\DDDToolkit.Core.csproj" />
13+
<ProjectReference Include="..\..\..\src\DDDToolkit.Validation\DDDToolkit.Validation.csproj" />
1314
</ItemGroup>
1415

1516
<ItemGroup>

samples/library/DDDToolkit.Samples.Library.Domain/ISBN.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ protected ISBN() { }
1515
[JsonConstructor]
1616
public ISBN(string value)
1717
{
18-
if(!IsValid(value))
19-
{
20-
throw new ArgumentException($"{value} is not a valid ISBN.", nameof(value));
21-
}
18+
2219
_value = value;
2320
}
2421

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using DDDToolkit.Querying;
2+
3+
namespace DDDToolkit.Samples.Library.Domain
4+
{
5+
public class AuthorHasFirstName : Query<Author>
6+
{
7+
public AuthorHasFirstName() : base(Has(a => a.FirstName).Satisfying(x => !string.IsNullOrWhiteSpace(x)))
8+
{
9+
}
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using DDDToolkit.Querying;
2+
3+
namespace DDDToolkit.Samples.Library.Domain
4+
{
5+
internal class AuthorHasLastName : Query<Author>
6+
{
7+
public AuthorHasLastName() : base(Has(a => a.LastName).Satisfying(x => !string.IsNullOrWhiteSpace(x)))
8+
{
9+
}
10+
}
11+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using DDDToolkit.Querying;
2+
3+
namespace DDDToolkit.Samples.Library.Domain
4+
{
5+
public class AuthorHasName : Query<Author>
6+
{
7+
public AuthorHasName(string firstName, string lastName)
8+
: base(
9+
Has(a => a.FirstName)
10+
.EqualTo(firstName)
11+
.And()
12+
.Has(a => a.LastName)
13+
.EqualTo(lastName)
14+
) { }
15+
}
16+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using DDDToolkit.Validation;
2+
using DDDToolkit.Querying;
3+
using DDDToolkit.Samples.Library.Domain.Validation;
4+
using System;
5+
6+
namespace DDDToolkit.Samples.Library.Domain
7+
{
8+
public class BookValidator : Validator<Book>
9+
{
10+
public BookValidator()
11+
{
12+
Property(b => b.ISBN)
13+
.HasRule<IsbnIsNumeric>("ISBN is not numeric")
14+
.HasRule<IsbnIsOfValidLength>();
15+
Property(b => b.Author)
16+
.HasRule<AuthorHasFirstName>()
17+
.HasRule<AuthorHasLastName>();
18+
Property(b => b.Title)
19+
.HasRule(Query<string>.Is.Satisfying(s => !string.IsNullOrEmpty(s)), "Title is required");
20+
}
21+
}
22+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using DDDToolkit.Querying;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Text;
5+
using System.Linq.Expressions;
6+
using System.Text.RegularExpressions;
7+
8+
namespace DDDToolkit.Samples.Library.Domain.Validation
9+
{
10+
public class IsbnIsNumeric : Query<ISBN>
11+
{
12+
public IsbnIsNumeric() :
13+
base(Has(i => i.Value).Satisfying(i => Regex.IsMatch(i, @"^\d+$")))
14+
{
15+
}
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using DDDToolkit.Querying;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Text;
5+
using System.Linq.Expressions;
6+
7+
namespace DDDToolkit.Samples.Library.Domain.Validation
8+
{
9+
public class IsbnIsOfValidLength : Query<ISBN>
10+
{
11+
public IsbnIsOfValidLength()
12+
: base(Has(i => i.Value.Length)
13+
.EqualToAnyOf(10, 13))
14+
{
15+
}
16+
}
17+
}

samples/library/DDDToolkit.Samples.Library.UI.Web/Controllers/AuthorController.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ public AuthorController(IApplicationService<Book, int> applicationService)
2121
}
2222

2323
[Route("{firstName} {lastName}/Books")]
24-
public Task<IReadOnlyCollection<Book>> GetBooksByAuthor(string firstName, string lastName)
24+
public async Task<IReadOnlyCollection<Book>> GetBooksByAuthor(string firstName, string lastName)
2525
{
26-
var query = Query<Book>.Has(b => b.Author.FirstName).EqualTo(firstName).And().Has(b => b.Author.LastName).EqualTo(lastName);
27-
return _applicationService.Query(query);
26+
var authorHasName = new AuthorHasName(firstName, lastName);
27+
var query = Query<Book>.Has(b => b.Author).Satisfying(authorHasName);
28+
var result = await _applicationService.Query(query);
29+
return result;
2830
}
2931
}
3032
}

samples/library/DDDToolkit.Samples.Library.UI.Web/Controllers/BookController.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
using DDDToolkit.API;
1+
using System.Threading.Tasks;
2+
using DDDToolkit.API;
23
using DDDToolkit.ApplicationLayer;
34
using DDDToolkit.Samples.Library.Domain;
45
using Microsoft.AspNetCore.Mvc;
6+
using System.Linq;
57

68
namespace DDDToolkit.Samples.Library.UI.Web.Controllers
79
{
@@ -12,5 +14,17 @@ public class BookController : AggregateController<Book, int>
1214
public BookController(IApplicationService<Book, int> applicationService) : base(applicationService)
1315
{
1416
}
17+
18+
[HttpPost]
19+
public async override Task<IActionResult> Create([FromBody] Book aggregate)
20+
{
21+
var validator = new BookValidator();
22+
var brokenRules = validator.Validate(aggregate);
23+
if(brokenRules.Any())
24+
{
25+
return BadRequest();
26+
}
27+
return await base.Create(aggregate);
28+
}
1529
}
1630
}

src/DDDToolkit.API/AggregateController.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,20 @@ protected AggregateController(IApplicationService<T, TId> applicationService)
1919

2020
[HttpGet]
2121
[Route("{id}")]
22-
public Task<T> GetById(TId id) => _readableAggregateController.GetById(id);
22+
public virtual Task<T> GetById(TId id) => _readableAggregateController.GetById(id);
2323

2424
[HttpGet]
25-
public Task<IReadOnlyCollection<T>> GetAll() => _readableAggregateController.GetAll();
25+
public virtual Task<IReadOnlyCollection<T>> GetAll() => _readableAggregateController.GetAll();
2626

2727
[HttpPost]
28-
public Task<IActionResult> Create([FromBody] T aggregate) => _writableAggregateController.Create(aggregate);
28+
public virtual Task<IActionResult> Create([FromBody] T aggregate) => _writableAggregateController.Create(aggregate);
2929

3030
[HttpPut]
3131
[Route("{id}")]
32-
public Task<IActionResult> Edit(TId id, [FromBody] T aggregate) => _writableAggregateController.Edit(id, aggregate);
32+
public virtual Task<IActionResult> Edit(TId id, [FromBody] T aggregate) => _writableAggregateController.Edit(id, aggregate);
3333

3434
[HttpDelete]
3535
[Route("{id}")]
36-
public Task<IActionResult> Delete(TId id) => _writableAggregateController.Delete(id);
36+
public virtual Task<IActionResult> Delete(TId id) => _writableAggregateController.Delete(id);
3737
}
3838
}

src/DDDToolkit.Querying/DDDToolkit.Querying.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,8 @@
1414
<PackageTags>DDD, Domain Driven Design, Querying, Queries, Query</PackageTags>
1515
</PropertyGroup>
1616

17+
<ItemGroup>
18+
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.4.0" />
19+
</ItemGroup>
20+
1721
</Project>

src/DDDToolkit.Querying/IQuery.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ public interface IQuery<T>
88
IQuery<T> And(IQuery<T> query);
99
IQuery<T> Or(IQuery<T> query);
1010
Expression<Func<T, bool>> AsExpression();
11-
bool EvaluateOn(T subject);
11+
bool IsSatisfiedBy(T subject);
1212
}
1313
}

src/DDDToolkit.Querying/ObjectExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ namespace DDDToolkit.Querying
66
{
77
public static class ObjectExtensions
88
{
9-
public static bool Evaluate<T>(this T obj, IQuery<T> query) => query.EvaluateOn(obj);
9+
public static bool Satisfies<T>(this T obj, IQuery<T> query) => query.IsSatisfiedBy(obj);
1010
}
1111
}

src/DDDToolkit.Querying/ParameterVisitor.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using System.Linq.Expressions;
45

56
namespace DDDToolkit.Querying
67
{
78
class ParameterVisitor : ExpressionVisitor
89
{
9-
private readonly IReadOnlyList<ParameterExpression> _from, _to;
10+
private readonly IReadOnlyList<ParameterExpression> _from;
11+
private readonly Expression[] _to;
1012
public ParameterVisitor(
1113
IReadOnlyList<ParameterExpression> from,
12-
IReadOnlyList<ParameterExpression> to)
14+
IReadOnlyList<ParameterExpression> to) : this(from, (Expression[])to.ToArray())
15+
{
16+
17+
}
18+
19+
public ParameterVisitor(
20+
IReadOnlyList<ParameterExpression> from,
21+
params Expression[] to)
1322
{
1423
if (from == null) throw new ArgumentNullException("from");
1524
if (to == null) throw new ArgumentNullException("to");
16-
if (from.Count != to.Count) throw new InvalidOperationException(
25+
if (from.Count != to.Length) throw new InvalidOperationException(
1726
"Parameter lengths must match");
1827
_from = from;
1928
_to = to;

src/DDDToolkit.Querying/Query.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ public Query(Expression<Func<T, bool>> expression)
1212
_expression = expression;
1313
}
1414

15+
public Query(IQuery<T> query)
16+
{
17+
_expression = query.AsExpression();
18+
}
19+
1520
private Query<T> CreateNewQuery(IQuery<T> query, Func<Expression, Expression, Expression> combiner)
1621
{
1722
var e2 = query.AsExpression();
@@ -24,16 +29,24 @@ private Query<T> CreateNewQuery(IQuery<T> query, Func<Expression, Expression, Ex
2429

2530
public IQuery<T> And(IQuery<T> query) => CreateNewQuery(query, Expression.AndAlso);
2631
public IQuery<T> Or(IQuery<T> query) => CreateNewQuery(query, Expression.OrElse);
32+
33+
public QueryBuilderContinuation<T> And()
34+
=> new QueryBuilderContinuation<T>((q2) => CreateNewQuery(q2, Expression.And));
35+
36+
public QueryBuilderContinuation<T> Or()
37+
=> new QueryBuilderContinuation<T>((q2) => CreateNewQuery(q2, Expression.Or));
2738

2839
public Expression<Func<T, bool>> AsExpression() => _expression;
2940

30-
public bool EvaluateOn(T subject) => AsExpression().Compile()(subject);
41+
public bool IsSatisfiedBy(T subject) => AsExpression().Compile()(subject);
3142

3243
public static Query<T> Has(Expression<Func<T, bool>> query) => new Query<T>(query);
3344

3445
public static QueryBuilderExpression<T, TProp> Has<TProp>(Expression<Func<T, TProp>> expression)
3546
=> new QueryBuilderExpression<T, TProp>(expression);
3647

3748
public static QueryBuilderExpression<T, T> Is => Has(e => e);
49+
50+
public static implicit operator Query<T>(Expression<Func<T, bool>> query) => new Query<T>(query);
3851
}
3952
}

src/DDDToolkit.Querying/QueryBuilder.cs

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/DDDToolkit.Querying/QueryBuilderContinuation.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ namespace DDDToolkit.Querying
55
{
66
public class QueryBuilderContinuation<T>
77
{
8-
private readonly Func<IQuery<T>, IQuery<T>> _continueWith;
8+
private readonly Func<Query<T>, Query<T>> _continueWith;
99

10-
internal QueryBuilderContinuation(Func<IQuery<T>, IQuery<T>> continueWith)
10+
internal QueryBuilderContinuation(Func<Query<T>, Query<T>> continueWith)
1111
{
1212
_continueWith = continueWith;
1313
}
1414

1515
public QueryBuilderExpression<T, T> Is => new QueryBuilderExpression<T, T>(e => e, _continueWith);
1616
public QueryBuilderExpression<T, TProp> Has<TProp>(Expression<Func<T, TProp>> expression)
1717
=> new QueryBuilderExpression<T, TProp>(expression, _continueWith);
18+
19+
public Query<T> Has(Expression<Func<T, bool>> query) => new Query<T>(query);
1820
}
1921
}

0 commit comments

Comments
 (0)