ABP Framework - Specification

Keywords: ASP.NET Database Lambda SQL

directory

 

Contents of this section:

 

brief introduction

Specification pattern is a special software design pattern. Business logic can use boolean logic to reconnect business logic.( Wikipedia).

In most cases in practice, it defines reusable filters for entities or other business objects.

 

Example

In this section, we will see the necessity of the protocol pattern. This section is general and has nothing to do with the implementation of ABP.

Suppose you have a service method to calculate the total number of customers, such as:

public class CustomerManager
{
    public int GetCustomerCount()
    {
        //TODO...
        return 0;
    }
}

You may want to get the number of customers through a filter, such as: you have high quality customers (the balance exceeds $100,000) or filter customers according to the year of registration, so you can create other methods, such as GetPremium Customer Count (), GetCustomer Count Registered InYear (int year), GetPremium Customer Count Registered InYear (int year), etc. When you have more conditions, it is impossible to create for each situation. Build a portfolio.

One solution to this problem is the specification pattern. We can create a separate method to get a parameter as a filter:

public class CustomerManager
{
    private readonly IRepository<Customer> _customerRepository;

    public CustomerManager(IRepository<Customer> customerRepository)
    {
        _customerRepository = customerRepository;
    }

    public int GetCustomerCount(ISpecification<Customer> spec)
    {
        var customers = _customerRepository.GetAllList();

        var customerCount = 0;
        
        foreach (var customer in customers)
        {
            if (spec.IsSatisfiedBy(customer))
            {
                customerCount++;
            }
        }

        return customerCount;
    }
}

Therefore, we can accept any object that implements the ISpecification < Customer > interface as a parameter, such as:

public interface ISpecification<T>
{
    bool IsSatisfiedBy(T obj);
}

Then we can call IsSatisfiedBy for a customer to check whether the customer is intended. Therefore, we can call the same GetCustomerCount method with different filters without modifying the method itself.

In theory, this scheme is good, but it can be improved more perfectly in C #. For example, it is not very efficient to get all the customers from the database to check whether they meet the given protocol/condition. In the next section, we will see the implementation of ABP, which solves this problem.

 

Create a specification class

ABP defines the following ISpecification interfaces:

public interface ISpecification<T>
{
    bool IsSatisfiedBy(T obj);

    Expression<Func<T, bool>> ToExpression();
} 

Add a ToExpression() method, which returns an expression to better combine IQueryable with the expression tree. Therefore, to accept a filter for the database level, we simply need to pass a protocol to a repository.

We usually inherit from the Specification <T> class rather than directly implement the ISpecification <T> interface. The Specification class automatically implements the IsSatisfiedBy method, so we just need to define ToExpression, let's create some specification classes:

//Customers with $100,000+ balance are assumed as PREMIUM customers.
public class PremiumCustomerSpecification : Specification<Customer>
{
    public override Expression<Func<Customer, bool>> ToExpression()
    {
        return (customer) => (customer.Balance >= 100000);
    }
}

//A parametric specification example.
public class CustomerRegistrationYearSpecification : Specification<Customer>
{
    public int Year { get; }

    public CustomerRegistrationYearSpecification(int year)
    {
        Year = year;
    }

    public override Expression<Func<Customer, bool>> ToExpression()
    {
        return (customer) => (customer.CreationYear == Year);
    }
}

As you can see, we are just implementing simple lambda expressions to define specifications. Let's use these specifications to get the number of customers:

count = customerManager.GetCustomerCount(new PremiumCustomerSpecification());
count = customerManager.GetCustomerCount(new CustomerRegistrationYearSpecification(2017));

 

Use of protocols in warehousing

Now we can optimize Customer Manager's acceptance of filters in the database:

public class CustomerManager
{
    private readonly IRepository<Customer> _customerRepository;

    public CustomerManager(IRepository<Customer> customerRepository)
    {
        _customerRepository = customerRepository;
    }

    public int GetCustomerCount(ISpecification<Customer> spec)
    {
        return _customerRepository.Count(spec.ToExpression());
    }
}

In this case, Customer Manager is not necessary, so we can query the database directly by using warehouse and specification, but consider: we want to perform a business operation on a customer, we can specify customers by using the specification and a domain service, and so on. Keep working.

 

Combinatorial conventions

There is a powerful function: you can use And,Or,Not and And Not extension methods to combine protocols. For example:

var count = customerManager.GetCustomerCount(new PremiumCustomerSpecification().And(new CustomerRegistrationYearSpecification(2017)));

We can even create a new protocol based on the existing one:

public class NewPremiumCustomersSpecification : AndSpecification<Customer>
{
    public NewPremiumCustomersSpecification() 
        : base(new PremiumCustomerSpecification(), new CustomerRegistrationYearSpecification(2017))
    {
    }
}

AndSpecification is a subclass that only meets the criteria if both protocols are satisfied. Then we can use New Premium Customers Specification, just like other specifications:

var count = customerManager.GetCustomerCount(new NewPremiumCustomersSpecification());

 

discuss

Since the specification pattern is older than the lambda expression in C#, by comparing it with the expression, some developers may think that the specification pattern is no longer needed, and we can pass the expression directly to a warehouse or domain service, such as:

var count = _customerRepository.Count(c => c.Balance > 100000 && c.CreationYear == 2017);

Since ABP's warehouse support expressions is a legitimate use, in applications, you do not have to define or use any conventions, so you can inherit the use of expressions, so what are the main points of the conventions? Why and when should you consider using them?

 

When to use

Benefits of using the Statute:

  • Reusable: Imagine that you need to use "quality customer" filtering in many places in your code. If you use expressions instead of creating a protocol, what happens if you need to modify the definition of "quality customer" in the future (e.g. you want to change the balance from at least $100,000 to 250,000 with other conditions, such as customer registration for more than three years). If you use the protocol, you only need to modify one class, such as If you use the same expression (copy/paste), you need to modify everything you use.
  • Composable: You can combine multiple protocols to create new ones, which is another form of reuse.
  • Naming: Premium Customer Specification better expresses intentions than a complex expression, so if you want an expression to become as its name implies in your business, consider using conventions.
  • Testability: A protocol is a separate (and easy) testable object.

 

When not?

  • No Business Expressions: You can use no conventions when your business does not involve expressions and operations.
  • Create reports: If you just create a report, you don't need specifications. Instead, you can use IQueryable directly. In fact, you can even use the original SQL, views or other reporting tools. DDD doesn't care about reports and underlying database storage for query benefits and view performance.

 

English original: http://www.aspnetboilerplate.com/Pages/Documents/Specifications

Posted by lupld on Fri, 29 Mar 2019 04:54:29 -0700