Skip to content

Querying Improvements and Validation #32

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

Merged
merged 11 commits into from
Jan 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
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
9 changes: 8 additions & 1 deletion DDDToolkit.sln
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "DDDToolkit.Samples.Library.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DDDToolkit.Querying", "src\DDDToolkit.Querying\DDDToolkit.Querying.csproj", "{AAAF943F-C3DE-4BD5-BC2D-850E77A9B607}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DDDToolkit.Querying.Tests", "DDDToolkit.Querying.Tests\DDDToolkit.Querying.Tests.csproj", "{4D18A7F4-169C-4399-BFF3-F2F8AE665C55}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DDDToolkit.Querying.Tests", "tests\DDDToolkit.Querying.Tests\DDDToolkit.Querying.Tests.csproj", "{4D18A7F4-169C-4399-BFF3-F2F8AE665C55}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DDDToolkit.Validation", "src\DDDToolkit.Validation\DDDToolkit.Validation.csproj", "{0D0A57EF-3B95-4D6B-A3E6-0217FEB0DC6F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -103,6 +105,10 @@ Global
{4D18A7F4-169C-4399-BFF3-F2F8AE665C55}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D18A7F4-169C-4399-BFF3-F2F8AE665C55}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4D18A7F4-169C-4399-BFF3-F2F8AE665C55}.Release|Any CPU.Build.0 = Release|Any CPU
{0D0A57EF-3B95-4D6B-A3E6-0217FEB0DC6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D0A57EF-3B95-4D6B-A3E6-0217FEB0DC6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D0A57EF-3B95-4D6B-A3E6-0217FEB0DC6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D0A57EF-3B95-4D6B-A3E6-0217FEB0DC6F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -123,6 +129,7 @@ Global
{1D41D8D2-B6A5-47DD-8E98-9512EC48B429} = {BDD881C3-118B-42D6-9D77-DDAB213CA4B0}
{AAAF943F-C3DE-4BD5-BC2D-850E77A9B607} = {EF054C6D-056E-417A-A40A-376A20DF90A6}
{4D18A7F4-169C-4399-BFF3-F2F8AE665C55} = {57967491-B4BB-44B9-B270-6533A1E388B0}
{0D0A57EF-3B95-4D6B-A3E6-0217FEB0DC6F} = {EF054C6D-056E-417A-A40A-376A20DF90A6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E3DCC906-DD65-4478-A617-AC943C719834}
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
# DDDToolkit
[<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)

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.

Find more in the [Wiki](https://github.com/codepb/DDDToolkit/wiki)
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

<ItemGroup>
<ProjectReference Include="..\..\..\src\DDDToolkit.Core\DDDToolkit.Core.csproj" />
<ProjectReference Include="..\..\..\src\DDDToolkit.Validation\DDDToolkit.Validation.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
5 changes: 1 addition & 4 deletions samples/library/DDDToolkit.Samples.Library.Domain/ISBN.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ protected ISBN() { }
[JsonConstructor]
public ISBN(string value)
{
if(!IsValid(value))
{
throw new ArgumentException($"{value} is not a valid ISBN.", nameof(value));
}

_value = value;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using DDDToolkit.Querying;

namespace DDDToolkit.Samples.Library.Domain
{
public class AuthorHasFirstName : Query<Author>
{
public AuthorHasFirstName() : base(Has(a => a.FirstName).Satisfying(x => !string.IsNullOrWhiteSpace(x)))
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using DDDToolkit.Querying;

namespace DDDToolkit.Samples.Library.Domain
{
internal class AuthorHasLastName : Query<Author>
{
public AuthorHasLastName() : base(Has(a => a.LastName).Satisfying(x => !string.IsNullOrWhiteSpace(x)))
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using DDDToolkit.Querying;

namespace DDDToolkit.Samples.Library.Domain
{
public class AuthorHasName : Query<Author>
{
public AuthorHasName(string firstName, string lastName)
: base(
Has(a => a.FirstName)
.EqualTo(firstName)
.And()
.Has(a => a.LastName)
.EqualTo(lastName)
) { }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using DDDToolkit.Validation;
using DDDToolkit.Querying;
using DDDToolkit.Samples.Library.Domain.Validation;
using System;

namespace DDDToolkit.Samples.Library.Domain
{
public class BookValidator : Validator<Book>
{
public BookValidator()
{
Property(b => b.ISBN)
.HasRule<IsbnIsNumeric>("ISBN is not numeric")
.HasRule<IsbnIsOfValidLength>();
Property(b => b.Author)
.HasRule<AuthorHasFirstName>()
.HasRule<AuthorHasLastName>();
Property(b => b.Title)
.HasRule(Query<string>.Is.Satisfying(s => !string.IsNullOrEmpty(s)), "Title is required");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using DDDToolkit.Querying;
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq.Expressions;
using System.Text.RegularExpressions;

namespace DDDToolkit.Samples.Library.Domain.Validation
{
public class IsbnIsNumeric : Query<ISBN>
{
public IsbnIsNumeric() :
base(Has(i => i.Value).Satisfying(i => Regex.IsMatch(i, @"^\d+$")))
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using DDDToolkit.Querying;
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq.Expressions;

namespace DDDToolkit.Samples.Library.Domain.Validation
{
public class IsbnIsOfValidLength : Query<ISBN>
{
public IsbnIsOfValidLength()
: base(Has(i => i.Value.Length)
.EqualToAnyOf(10, 13))
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ public AuthorController(IApplicationService<Book, int> applicationService)
}

[Route("{firstName} {lastName}/Books")]
public Task<IReadOnlyCollection<Book>> GetBooksByAuthor(string firstName, string lastName)
public async Task<IReadOnlyCollection<Book>> GetBooksByAuthor(string firstName, string lastName)
{
var query = Query<Book>.Has(b => b.Author.FirstName).EqualTo(firstName).And().Has(b => b.Author.LastName).EqualTo(lastName);
return _applicationService.Query(query);
var authorHasName = new AuthorHasName(firstName, lastName);
var query = Query<Book>.Has(b => b.Author).Satisfying(authorHasName);
var result = await _applicationService.Query(query);
return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using DDDToolkit.API;
using System.Threading.Tasks;
using DDDToolkit.API;
using DDDToolkit.ApplicationLayer;
using DDDToolkit.Samples.Library.Domain;
using Microsoft.AspNetCore.Mvc;
using System.Linq;

namespace DDDToolkit.Samples.Library.UI.Web.Controllers
{
Expand All @@ -12,5 +14,17 @@ public class BookController : AggregateController<Book, int>
public BookController(IApplicationService<Book, int> applicationService) : base(applicationService)
{
}

[HttpPost]
public async override Task<IActionResult> Create([FromBody] Book aggregate)
{
var validator = new BookValidator();
var brokenRules = validator.Validate(aggregate);
if(brokenRules.Any())
{
return BadRequest();
}
return await base.Create(aggregate);
}
}
}
10 changes: 5 additions & 5 deletions src/DDDToolkit.API/AggregateController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ protected AggregateController(IApplicationService<T, TId> applicationService)

[HttpGet]
[Route("{id}")]
public Task<T> GetById(TId id) => _readableAggregateController.GetById(id);
public virtual Task<T> GetById(TId id) => _readableAggregateController.GetById(id);

[HttpGet]
public Task<IReadOnlyCollection<T>> GetAll() => _readableAggregateController.GetAll();
public virtual Task<IReadOnlyCollection<T>> GetAll() => _readableAggregateController.GetAll();

[HttpPost]
public Task<IActionResult> Create([FromBody] T aggregate) => _writableAggregateController.Create(aggregate);
public virtual Task<IActionResult> Create([FromBody] T aggregate) => _writableAggregateController.Create(aggregate);

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

[HttpDelete]
[Route("{id}")]
public Task<IActionResult> Delete(TId id) => _writableAggregateController.Delete(id);
public virtual Task<IActionResult> Delete(TId id) => _writableAggregateController.Delete(id);
}
}
4 changes: 4 additions & 0 deletions src/DDDToolkit.Querying/DDDToolkit.Querying.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@
<PackageTags>DDD, Domain Driven Design, Querying, Queries, Query</PackageTags>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.4.0" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion src/DDDToolkit.Querying/IQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ public interface IQuery<T>
IQuery<T> And(IQuery<T> query);
IQuery<T> Or(IQuery<T> query);
Expression<Func<T, bool>> AsExpression();
bool EvaluateOn(T subject);
bool IsSatisfiedBy(T subject);
}
}
2 changes: 1 addition & 1 deletion src/DDDToolkit.Querying/ObjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ namespace DDDToolkit.Querying
{
public static class ObjectExtensions
{
public static bool Evaluate<T>(this T obj, IQuery<T> query) => query.EvaluateOn(obj);
public static bool Satisfies<T>(this T obj, IQuery<T> query) => query.IsSatisfiedBy(obj);
}
}
15 changes: 12 additions & 3 deletions src/DDDToolkit.Querying/ParameterVisitor.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace DDDToolkit.Querying
{
class ParameterVisitor : ExpressionVisitor
{
private readonly IReadOnlyList<ParameterExpression> _from, _to;
private readonly IReadOnlyList<ParameterExpression> _from;
private readonly Expression[] _to;
public ParameterVisitor(
IReadOnlyList<ParameterExpression> from,
IReadOnlyList<ParameterExpression> to)
IReadOnlyList<ParameterExpression> to) : this(from, (Expression[])to.ToArray())
{

}

public ParameterVisitor(
IReadOnlyList<ParameterExpression> from,
params Expression[] to)
{
if (from == null) throw new ArgumentNullException("from");
if (to == null) throw new ArgumentNullException("to");
if (from.Count != to.Count) throw new InvalidOperationException(
if (from.Count != to.Length) throw new InvalidOperationException(
"Parameter lengths must match");
_from = from;
_to = to;
Expand Down
15 changes: 14 additions & 1 deletion src/DDDToolkit.Querying/Query.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ public Query(Expression<Func<T, bool>> expression)
_expression = expression;
}

public Query(IQuery<T> query)
{
_expression = query.AsExpression();
}

private Query<T> CreateNewQuery(IQuery<T> query, Func<Expression, Expression, Expression> combiner)
{
var e2 = query.AsExpression();
Expand All @@ -24,16 +29,24 @@ private Query<T> CreateNewQuery(IQuery<T> query, Func<Expression, Expression, Ex

public IQuery<T> And(IQuery<T> query) => CreateNewQuery(query, Expression.AndAlso);
public IQuery<T> Or(IQuery<T> query) => CreateNewQuery(query, Expression.OrElse);

public QueryBuilderContinuation<T> And()
=> new QueryBuilderContinuation<T>((q2) => CreateNewQuery(q2, Expression.And));

public QueryBuilderContinuation<T> Or()
=> new QueryBuilderContinuation<T>((q2) => CreateNewQuery(q2, Expression.Or));

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

public bool EvaluateOn(T subject) => AsExpression().Compile()(subject);
public bool IsSatisfiedBy(T subject) => AsExpression().Compile()(subject);

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

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

public static QueryBuilderExpression<T, T> Is => Has(e => e);

public static implicit operator Query<T>(Expression<Func<T, bool>> query) => new Query<T>(query);
}
}
19 changes: 0 additions & 19 deletions src/DDDToolkit.Querying/QueryBuilder.cs

This file was deleted.

6 changes: 4 additions & 2 deletions src/DDDToolkit.Querying/QueryBuilderContinuation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ namespace DDDToolkit.Querying
{
public class QueryBuilderContinuation<T>
{
private readonly Func<IQuery<T>, IQuery<T>> _continueWith;
private readonly Func<Query<T>, Query<T>> _continueWith;

internal QueryBuilderContinuation(Func<IQuery<T>, IQuery<T>> continueWith)
internal QueryBuilderContinuation(Func<Query<T>, Query<T>> continueWith)
{
_continueWith = continueWith;
}

public QueryBuilderExpression<T, T> Is => new QueryBuilderExpression<T, T>(e => e, _continueWith);
public QueryBuilderExpression<T, TProp> Has<TProp>(Expression<Func<T, TProp>> expression)
=> new QueryBuilderExpression<T, TProp>(expression, _continueWith);

public Query<T> Has(Expression<Func<T, bool>> query) => new Query<T>(query);
}
}
Loading