Skip to content
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

specifications: #72

Merged
merged 6 commits into from
Sep 28, 2021
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
4 changes: 3 additions & 1 deletion samples/SimplestApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System;
using SimplestApp.Operations;
using SimplestApp.Services;
using SimplestApp.Specifications;

namespace SimplestApp
{
Expand All @@ -16,7 +17,8 @@ static void Main(string[] args)
{
Console.WriteLine("== Simple App to Create a User");

UserService us = new UserService();
var spec = new UserValidSpecification();
UserService us = new UserService(spec);

var user = us.Add(new AddUser {Name = "My name", Mail = "[email protected]"});

Expand Down
11 changes: 9 additions & 2 deletions samples/SimplestApp/Services/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

using System;
using DFlow.Domain.Specifications;
using DFlow.Samples.BusinessObjects.Domain.BusinessObjects;
using DFlow.Samples.Domain.Aggregates;
using DFlow.Samples.Domain.BusinessObjects;
Expand All @@ -14,13 +15,19 @@ namespace SimplestApp.Services
{
public class UserService:IUserService
{
private ISpecification<UserEntityBasedAggregationRoot> _specification;
public UserService(ISpecification<UserEntityBasedAggregationRoot> specification)
{
_specification = specification;
}

public User Add(AddUser user)
{
var agg = UserEntityBasedAggregationRoot.CreateFrom(Name.From(user.Name), Email.From(user.Mail));

if (!agg.IsValid)
if (!_specification.IsSatisfiedBy(agg))
{
throw new ArgumentException("One or more parameters informed to create a user are not valid.");
throw new ArgumentException(agg.Failures[0].ErrorMessage);
}

return agg.GetChange();
Expand Down
44 changes: 44 additions & 0 deletions samples/SimplestApp/Specifications/UserService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (C) 2020 Road to Agility
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

using System;
using DFlow.Domain.Specifications;
using DFlow.Samples.Domain.Aggregates;
using DFlow.Samples.Domain.BusinessObjects;
using FluentValidation.Results;
using SimplestApp.Operations;
using SimplestApp.Services;

namespace SimplestApp.Specifications
{
public class UserValidSpecification:CompositeSpecification<UserEntityBasedAggregationRoot>
{

public User Add(AddUser user)
{
var agg = UserEntityBasedAggregationRoot.CreateFrom(Name.From(user.Name), Email.From(user.Mail));

if (!agg.IsValid)
{
throw new ArgumentException("One or more parameters informed to create a user are not valid.");
}

return agg.GetChange();
}

public override bool IsSatisfiedBy(UserEntityBasedAggregationRoot candidate)
{
if (!candidate.IsValid)
{
candidate.AppendValidationResult(new ValidationFailure("InvalidUser",
"One or more parameters informed to create a user are not valid."));
return false;
}

return true;
}
}
}
1 change: 1 addition & 0 deletions src/DFlow.Business.Cqrs/CommandHandlers/CommandResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

using System.Collections.Generic;
using DFlow.Domain.Validation;
using FluentValidation.Results;

namespace DFlow.Business.Cqrs.CommandHandlers
Expand Down
22 changes: 22 additions & 0 deletions src/DFlow.Domain/Specifications/AndSpecification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (C) 2021 Road to Agility
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

namespace DFlow.Domain.Specifications
{
public class AndSpecification<TBusinessObject>:LogicalSpecification<TBusinessObject>
{
public AndSpecification(ISpecification<TBusinessObject> leftCondition, ISpecification<TBusinessObject> rightCondition)
: base(leftCondition, rightCondition)
{
}

public override bool IsSatisfiedBy(TBusinessObject candidate)
{
return LeftCondition.IsSatisfiedBy(candidate)
&& RightCondition.IsSatisfiedBy(candidate);
}
}
}
32 changes: 32 additions & 0 deletions src/DFlow.Domain/Specifications/CompositeSpecification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (C) 2021 Road to Agility
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

using System.Collections.Generic;
using System.Collections.Immutable;

namespace DFlow.Domain.Specifications
{
public abstract class CompositeSpecification<TBusinessObject>:ISpecification<TBusinessObject>
{
public ISpecification<TBusinessObject> And(ISpecification<TBusinessObject> candidate)
{
return new AndSpecification<TBusinessObject>(this,candidate);
}

public ISpecification<TBusinessObject> Or(ISpecification<TBusinessObject> candidate)
{
return new OrSpecification<TBusinessObject>(this,candidate);

}

public ISpecification<TBusinessObject> Not()
{
return new NotSpecification<TBusinessObject>(this);
}

public abstract bool IsSatisfiedBy(TBusinessObject candidate);
}
}
18 changes: 18 additions & 0 deletions src/DFlow.Domain/Specifications/ISpecification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (C) 2021 Road to Agility
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

using System.Collections.Generic;

namespace DFlow.Domain.Specifications
{
public interface ISpecification<TBusinessObject>
{
bool IsSatisfiedBy(TBusinessObject candidate);
ISpecification<TBusinessObject> And(ISpecification<TBusinessObject> other);
ISpecification<TBusinessObject> Or(ISpecification<TBusinessObject> other);
ISpecification<TBusinessObject> Not();
}
}
20 changes: 20 additions & 0 deletions src/DFlow.Domain/Specifications/LinqExpressionSpecification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (C) 2021 Road to Agility
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

using System;
using System.Linq.Expressions;

namespace DFlow.Domain.Specifications
{
public abstract class LinqExpressionSpecification<TBusinessObject>
:CompositeSpecification<TBusinessObject>
{
protected abstract Expression<Func<TBusinessObject, bool>> AsExpression();

public override bool IsSatisfiedBy(TBusinessObject candidate)
=> AsExpression().Compile()(candidate);
}
}
22 changes: 22 additions & 0 deletions src/DFlow.Domain/Specifications/LogicalSpecification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (C) 2021 Road to Agility
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

namespace DFlow.Domain.Specifications
{
public abstract class LogicalSpecification<TBusinessObject>:CompositeSpecification<TBusinessObject>
{
protected ISpecification<TBusinessObject> LeftCondition { get; }

protected ISpecification<TBusinessObject> RightCondition { get; }

protected LogicalSpecification(ISpecification<TBusinessObject> leftCondition,
ISpecification<TBusinessObject> rightCondition)
{
LeftCondition = leftCondition;
RightCondition = rightCondition;
}
}
}
23 changes: 23 additions & 0 deletions src/DFlow.Domain/Specifications/NotSpecification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (C) 2021 Road to Agility
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

namespace DFlow.Domain.Specifications
{
public class NotSpecification<TBusinessObject>:CompositeSpecification<TBusinessObject>
{
protected ISpecification<TBusinessObject> Condition { get; }

public NotSpecification(ISpecification<TBusinessObject> condition)
{
Condition = condition;
}

public override bool IsSatisfiedBy(TBusinessObject candidate)
{
return !Condition.IsSatisfiedBy(candidate);
}
}
}
22 changes: 22 additions & 0 deletions src/DFlow.Domain/Specifications/OrSpecification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (C) 2021 Road to Agility
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

namespace DFlow.Domain.Specifications
{
public class OrSpecification<TBusinessObject>:LogicalSpecification<TBusinessObject>
{
public OrSpecification(ISpecification<TBusinessObject> leftCondition, ISpecification<TBusinessObject> rightCondition)
: base(leftCondition, rightCondition)
{
}

public override bool IsSatisfiedBy(TBusinessObject candidate)
{
return LeftCondition.IsSatisfiedBy(candidate)
|| RightCondition.IsSatisfiedBy(candidate);
}
}
}
7 changes: 6 additions & 1 deletion src/DFlow.Domain/Validation/BaseValidation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@

using System.Collections.Generic;
using System.Collections.Immutable;
using DFlow.Domain.BusinessObjects;
using FluentValidation.Results;

namespace DFlow.Domain.Validation
{
public abstract class BaseValidation: IValidable
{
private readonly List<ValidationFailure> _failures = new List<ValidationFailure>();

public void AppendValidationResult(ValidationFailure failure)
{
_failures.Add(failure);
}

public void AppendValidationResult(IReadOnlyList<ValidationFailure> failures)
{
_failures.AddRange(failures);
Expand Down
2 changes: 2 additions & 0 deletions src/DFlow.Domain/Validation/IValidable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ namespace DFlow.Domain.Validation
public interface IValidable
{
bool IsValid { get; }
void AppendValidationResult(ValidationFailure failure);

void AppendValidationResult(IReadOnlyList<ValidationFailure> failures);
}
}
7 changes: 7 additions & 0 deletions src/DFlow.Specifications/DFlow.Specifications.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net5.0;netcoreapp3.1</TargetFrameworks>
</PropertyGroup>

</Project>
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<VersionPrefix>0.3.0</VersionPrefix>
<VersionPrefix>0.4.0</VersionPrefix>
<VersionSuffix>pre</VersionSuffix>
<!-- <PackageProjectUrl>https://github.com/roadtoagility/dflow</PackageProjectUrl>
<PackageIconUrl>https://github.com/roadtoagility/dflow/raw/master/docs/img/logodflow_200x200.png</PackageIconUrl> -->
Expand Down
1 change: 0 additions & 1 deletion tests/DFlow.Tests/Domain/EntityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.


using System;
using AutoFixture;
using DFlow.Domain.BusinessObjects;
using DFlow.Tests.Supporting.DomainObjects;
Expand Down
60 changes: 60 additions & 0 deletions tests/DFlow.Tests/Domain/SpecificationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (C) 2021 Road to Agility
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.


using DFlow.Domain.BusinessObjects;
using DFlow.Tests.Supporting.DomainObjects;
using DFlow.Tests.Supporting.Specifications;
using Xunit;

namespace DFlow.Tests.Domain
{
public class SpecificationForCreationEntityTests
{
[Fact]
public void BusinessEntityIsNew()
{
var bu = BusinessEntity.New();
var isNew = new BusinessEntityIsNew();

Assert.True(isNew.IsSatisfiedBy(bu));
}

[Fact]
public void BusinessEntityNotIsNew()
{
var buUpdated = BusinessEntity.From(EntityTestId.GetNext(), VersionId.Next(VersionId.New()));
var isNew = new BusinessEntityIsNew();

Assert.False(isNew.IsSatisfiedBy(buUpdated));
}

[Fact]
public void AnotherBusinessEntityNameIsRoad()
{
var bu = AnotherBusinessEntity
.New(Name.From("Road"), Email.From("[email protected]"));

var isRoad = new AnotherBusinessEntityNameIsRoad(Name.From("Road"));

Assert.True(isRoad.IsSatisfiedBy(bu));
}

[Fact]
public void AnotherBusinessEntityNameIsRoadAndEmailFromRoadCompany()
{
var bu = AnotherBusinessEntity
.New(Name.From("Road"), Email.From("[email protected]"));

var isRoad = new AnotherBusinessEntityNameIsRoad(Name.From("Road"));
var isFromCompany = new AnotherBusinessEntityEmailFromCompany(Email.From("[email protected]"));

isRoad.And(isFromCompany);

Assert.True(isRoad.IsSatisfiedBy(bu));
}
}
}
Loading