Skip to content
Aaron Hanusa edited this page Feb 1, 2021 · 82 revisions

peasy

A full implementation

A full implementation of a middle tier built with peasy and sample consumer clients (WPF, Web API, etc.) can be found in the Github repo here. You can clone the repo or download the entire project as a zip.

Once downloaded, open Orders.com.sln with Visual Studio, set the WPF or ASP.NET MVC project as the startup project and run. More information about the samples application can be found here.

The simplest possible example

Feeling lazy? You can download this sample!

Start by creating a solution with a class library project. Then install the peasy nuget package or clone the repo and set a project reference accordingly.

Next create a domain object (DTO) that implements IDomainObject<T>:

using Peasy;

public class Person : IDomainObject<int>
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string City { get; set; }
    public int Age { get; set; }
}

Then create a data proxy (aka repository) that implements IDataProxy<T, TKey> (most method implementations left out for brevity):

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Peasy;

namespace samples
{
    public class PersonStubDataProxy : IDataProxy<Person, int>
    {
        public Task<IEnumerable<Person>> GetAllAsync()
        {
            return Task.FromResult<IEnumerable<Person>>
            (
                new []
                {
                    new Person() { ID = 1, Name = "Jimi Hendrix" },
                    new Person() { ID = 2, Name = "James Page" },
                    new Person() { ID = 3, Name = "David Gilmour" }
                }
            );
        }

        public Task<Person> InsertAsync(Person entity)
        {
            return Task.FromResult(new Person() { ID = new Random(300).Next(), Name = entity.Name });
        }

        public Task DeleteAsync(int id)
        {
            throw new NotImplementedException();
        }

        public Task<Person> GetByIDAsync(int id)
        {
            throw new NotImplementedException();
        }

        public Task<Person> UpdateAsync(Person entity)
        {
            throw new NotImplementedException();
        }
    }
}

Finally, create a service class, which exposes CRUD commands responsible for subjecting IDataProxy invocations to business rules before execution:

using Peasy;

public class PersonService : ServiceBase<Person, int>
{
    public PersonService(IDataProxy<Person, int> dataProxy) : base(dataProxy)
    {
    }
}

Now let's consume our PersonService:

var service = new PersonService(new PersonStubDataProxy());
var getResult = await service.GetAllCommand().ExecuteAsync();

if (getResult.Success)
{
    foreach (var person in getResult.Value)
        Debug.WriteLine(person.Name);  // prints each person's name retrieved from PersonMockDataProxy.GetAll
}

var newPerson = new Person() { Name = "Freed Jones", City = "Madison", Age = 32 };
var insertResult = await service.InsertCommand(newPerson).ExecuteAsync();

if (insertResult.Success)
{
    Debug.WriteLine(insertResult.Value.ID.ToString()); // prints the id value assigned via PersonMockDataProxy.Insert
}

Let's create a business rule whose execution must be successful before the call to IDataProxy.InsertAsync is invoked

using System.Threading.Tasks;
using Peasy;

public class ValidAgeRule : RuleBase
{
    private int _age;

    public ValidAgeRule(int age)
    {
        _age = age;
    }

    protected override Task OnValidateAsync()
    {
        return IfNot(() => _age >= 21)
            .ThenInvalidateWith("You are not old enough");

        // You can use this syntax too if you prefer:
        // if (_age < 21)
        // {
        //     Invalidate("You are not old enough");
        // }
        // return Task.CompletedTask;
    }
}

And wire it up in our PersonService to ensure that it gets fired before inserts:

using System.Collections.Generic;
using System.Threading.Tasks;
using Peasy;
using Peasy.Extensions;

public class PersonService : ServiceBase<Person, int>
{
    public PersonService(IDataProxy<Person, int> dataProxy) : base(dataProxy)
    {
    }

    protected override Task<IEnumerable<IRule>> OnInsertCommandGetRulesAsync(Person resource, ExecutionContext<Person> context)
    {
        return TheseRules(new ValidAgeRule(resource.Age));

        // You can use this syntax too if you prefer:
        // return Task.FromResult(new ValidAgeRule(resource.Age).ToArray());
    }
}

Testing it out (be sure to add a reference to System.ComponentModel.Annotations)

var service = new PersonService(new PersonStubDataProxy());
var newPerson = new Person() { Name = "Fred Jones", City = "Madison", Age = 18 };
var insertResult = await service.InsertCommand(newPerson).ExecuteAsync();

if (insertResult.Success)
{
    Debug.WriteLine(insertResult.Value.ID.ToString());
}
else
{
    // This line will execute and print 'You are not old enough'
    // Note that insertResult.Value will be NULL as PersonStubDataProxy.Insert did not execute due to failed rule
    Debug.WriteLine(insertResult.Errors.First());
}

Let's create one more rule, just for fun:

using System.Threading.Tasks;
using Peasy;

public class ValidCityRule : RuleBase
{
    private string _city;

    public ValidCityRule(string city)
    {
        _city = city;
    }

    protected override Task OnValidateAsync()
    {
        return If(() => _city == "Nowhere")
            .ThenInvalidateWith("Nowhere is not a city");

        // You can use this syntax too if you prefer:
        // if (_city == "Nowhere")
        // {
        //     Invalidate("Nowhere is not a city");
        // }
        // return Task.CompletedTask;
    }
}

We'll associate this one with inserts too:

using System.Collections.Generic;
using System.Threading.Tasks;
using Peasy;

public class PersonService : ServiceBase<Person, int>
{
    public PersonService(IDataProxy<Person, int> dataProxy) : base(dataProxy)
    {
    }

    protected override Task<IEnumerable<IRule>> OnInsertCommandGetRulesAsync(Person resource, ExecutionContext<Person> context)
    {
        return TheseRules
        (
            new ValidAgeRule(resource.Age),
            new ValidCityRule(resource.City)
        );

        // You can use syntax like this too if you prefer:
        // return Task.FromResult<IEnumerable<IRule>>(new IRule[]
        // {
        //     new ValidAgeRule(resource.Age),
        //     new ValidCityRule(resource.City)
        // });
    }
}

And test it out (be sure to add a reference to System.ComponentModel.Annotations)

var service = new PersonService(new PersonStubDataProxy());
var newPerson = new Person() { Name = "Fred Jones", City = "Nowhere", Age = 18 };
var insertResult = await service.InsertCommand(newPerson).ExecuteAsync();

if (insertResult.Success)
{
    Debug.WriteLine(insertResult.Value.ID.ToString());
}
else
{
    // This line will execute and print 'You are not old enough' and 'Nowhere is not a city'
    // Note that insertResult.Value will be NULL as PersonStubDataProxy.Insert did not execute due to failed rule
    foreach (var error in insertResult.Errors)
        Debug.WriteLine(error);
}

Finally, let's supply valid data and watch it be a success

var service = new PersonService(new PersonStubDataProxy());
var newPerson = new Person() { Name = "Fred Jones", City = "Madison", Age = 21 };
var insertResult = await service.InsertCommand(newPerson).ExecuteAsync();

if (insertResult.Success)
{
    Debug.WriteLine(insertResult.Value.ID.ToString()); // prints the id value assigned via PersonStubDataProxy.Insert
}
else
{
    foreach (var error in insertResult.Errors)
        Debug.WriteLine(error);
}
Clone this wiki locally