Async spec template

I am trying to apply a specification template to my validation logic. But I have some problems with asynchronous validation.

Let's say I have an AddRequest entity (has 2 string properties FileName and Content) that need to be checked.

I need to create 3 validators:

  • Check to see if filename contains invalid characters

  • Validate Content

  • Async checks to see if a file named FileName exists in the database. In this case, I should have something like Task<bool> IsSatisfiedByAsync

But how can I implement both IsSatisfiedBy and IsSatisfiedByAsync ? Should I create 2 interfaces, for example ISpecification and IAsyncSpecification , or can I do this in one?

My version of ISpecification (I only need And)

  public interface ISpecification { bool IsSatisfiedBy(object candidate); ISpecification And(ISpecification other); } 

Andspecification

 public class AndSpecification : CompositeSpecification { private ISpecification leftCondition; private ISpecification rightCondition; public AndSpecification(ISpecification left, ISpecification right) { leftCondition = left; rightCondition = right; } public override bool IsSatisfiedBy(object o) { return leftCondition.IsSatisfiedBy(o) && rightCondition.IsSatisfiedBy(o); } } 

To check if a file exists, I should use:

  await _fileStorage.FileExistsAsync(addRequest.FileName); 

How can I write IsSatisfiedBy for this check, do I really need this asynchronous?

For example, here is my validator (1) for FileName

 public class FileNameSpecification : CompositeSpecification { private static readonly char[] _invalidEndingCharacters = { '.', '/' }; public override bool IsSatisfiedBy(object o) { var request = (AddRequest)o; if (string.IsNullOrEmpty(request.FileName)) { return false; } if (request.FileName.Length > 1024) { return false; } if (request.FileName.Contains('\\') || _invalidEndingCharacters.Contains(request.FileName.Last())) { return false; } return true } } 

I need to create FileExistsSpecification and use as:

 var validations = new FileNameSpecification().And(new FileExistsSpecification()); if(validations.IsSatisfiedBy(addRequest)) { ... } 

But how can I create FileExistsSpecification if I need async?

+5
source share
3 answers

But how can I implement both IsSatisfiedBy and IsSatisfiedByAsync? Should I create 2 interfaces like ISpecification and IAsyncSpecification, or can I do this in one?

You can define both synchronous and asynchronous interfaces, but any combined general-purpose implementations will only need to implement the asynchronous version.

Since asynchronous methods on interfaces mean “it can be asynchronous” , while synchronous methods mean “it should be synchronous”, I would go with an asynchronous interface, as such:

 public interface ISpecification { Task<bool> IsSatisfiedByAsync(object candidate); } 

If many of your specifications are synchronous, you can help with the base class:

 public abstract class SynchronousSpecificationBase : ISpecification { public virtual Task<bool> IsSatisfiedByAsync(object candidate) { return Task.FromResult(IsSatisfiedBy(candidate)); } protected abstract bool IsSatisfiedBy(object candidate); } 

Then the composites will:

 public class AndSpecification : ISpecification { ... public async Task<bool> IsSatisfiedByAsync(object o) { return await leftCondition.IsSatisfiedByAsync(o) && await rightCondition.IsSatisfiedByAsync(o); } } public static class SpecificationExtensions { public static ISpecification And(ISpeicification @this, ISpecification other) => new AndSpecification(@this, other); } 

and individual specifications as such:

 public class FileExistsSpecification : ISpecification { public async Task<bool> IsSatisfiedByAsync(object o) { return await _fileStorage.FileExistsAsync(addRequest.FileName); } } public class FileNameSpecification : SynchronousSpecification { private static readonly char[] _invalidEndingCharacters = { '.', '/' }; public override bool IsSatisfiedBy(object o) { var request = (AddRequest)o; if (string.IsNullOrEmpty(request.FileName)) return false; if (request.FileName.Length > 1024) return false; if (request.FileName.Contains('\\') || _invalidEndingCharacters.Contains(request.FileName.Last())) return false; return true; } } 

Using:

 var validations = new FileNameSpecification().And(new FileExistsSpecification()); if (await validations.IsSatisfiedByAsync(addRequest)) { ... } 
+2
source

I don't know why you need async operations in a sync template.

Imagine if the first result is false and you have two or more asynchronous checks, this will be a waste of performance.

If you want to know how to get an asynchronous request request, you can try using the following:

 public class FileExistsSpecification : CompositeSpecification { public override bool IsSatisfiedBy(object o) { var addRequest = (AddRequest)o Task<bool> fileExistsResult = _fileStorage.FileExistsAsync(addRequest.FileName); fileExistsResult.Wait(); return fileExistsResult.Result; } } 

You should also use the generics approach.

-1
source

I think your main goal here is to make sure that the code ends as soon as possible to evaluate the composite specification, when the execution of child specifications and one or more may take some time, right? You can always call code outside the template implementation to asynchronously call the specification; this is actually not your concern.

So, in light of this, how about providing your ISpecification with additional property?

 public interface ISpecification { bool IsAsynchronous { get; } bool IsSatisfiedBy(object o); } 

Then for non-matching synchronous or asynchronous specifications, the hard code returns IsAsynchronous. But in composite, base it on subsidiaries, namely:

 public class AndSpecification : ISpecification { private ISpecification left; private ISpecification right; public AndSpecification(ISpecification _left, ISpecification _right) { if (_left == null || _right == null) throw new ArgumentNullException(); left = _left; right = _right; } public bool IsAsynchronous { get { return left.IsAsynchronous || right.IsAsynchronous; } public override bool IsSatisfiedBy(object o) { if (!this.IsAsynchronous) return leftCondition.IsSatisfiedBy(o) && rightCondition.IsSatisfiedBy(o); Parallel.Invoke( () => { if (!left.IsSatisfiedBy(o)) return false; }, () => { if (!right.IsSatisfiedBy(o)) return false; } ); return true; } } 

But, taking this a little further, you do not want to lose performance. Therefore, why not rate the fast, synchronous child first when there is one synchronization and one asynchronous? Here is an approximate version of the basic idea:

 public class AndSpecification : ISpecification { private ISpecification left; private ISpecification right; public AndSpecification(ISpecification _left, ISpecification _right) { if (_left == null || _right == null) throw new ArgumentNullException(); left = _left; right = _right; } public bool IsAsynchronous { get { return left.IsAsynchronous || right.IsAsynchronous; } public override bool IsSatisfiedBy(object o) { if (!left.IsAsynchronous) { if (!right.IsAsynchronous) { return left.IsSatisfiedBy(o) && right.IsSatisfiedBy(o); } else { if (!left.IsSatisfiedBy(o)) return false; return right.IsSatisfiedBy(o); } } else if (!right.IsAsynchronous) { if (!right.IsSatisfiedBy(o)) return false; return left.IsSatisfiedBy(o); } else { Parallel.Invoke( () => { if (!left.IsSatisfiedBy(o)) return false; }, () => { if (!right.IsSatisfiedBy(o)) return false; } ); return true; } } } 
-1
source

Source: https://habr.com/ru/post/1270856/


All Articles