当前位置:网站首页>ABP framework Practice Series (III) - domain layer in depth

ABP framework Practice Series (III) - domain layer in depth

2022-06-26 03:44:00 yuyue5945

ABP Domain layer - Entity (Entities)

Entity is DDD( Domain-driven design ) One of the core concepts of .Eric Evans It's described like this “ Many objects are not defined by their properties , It is defined by a series of continuous events and signs ”.

In the frame combat series ( Two )- Introduction to domain layer There is a brief introduction to the code in ( Recommend the official account , Dry cargo is full. )

Here we have a deep understanding of the concept of entity

stay ABP in , Entity class inheritance Entity class , As shown below ,Product Is an entity

    public class Product : Entity
    {
    
        public virtual string Name {
     get; set; }
        public virtual string Code {
     get; set; }
        public virtual DateTime CreationTime {
     get; set; }

        public Product()
        {
    
            CreationTime=DateTime.Now;
        }

    }

stay ABP In the frame Entity The content of entity class is as follows , It has a primary key ID, All inherited from Entity Class , All use this primary key , You can set the value of the primary key to any format , As shown in the following code , It is generic , It can be for string,Guid Or other data types . At the same time, the entity class overrides equality (==) Operator is used to determine whether two entity objects are equal ( Of two entities Id Whether it is equal or not ). I've also defined one IsTransient() Method to detect whether the entity has Id attribute .

public abstract class Entity<TPrimaryKey> : IEntity<TPrimaryKey>
  {
    
    /// <summary>Unique identifier for this entity.</summary>
    public virtual TPrimaryKey Id {
     get; set; }

    /// <summary>
    /// Checks if this entity is transient (it has not an Id).
    /// </summary>
    /// <returns>True, if this entity is transient</returns>
    public virtual bool IsTransient()
    {
    
      if (EqualityComparer<TPrimaryKey>.Default.Equals(this.Id, default (TPrimaryKey)))
        return true;
      if (typeof (TPrimaryKey) == typeof (int))
        return Convert.ToInt32((object) this.Id) <= 0;
      return typeof (TPrimaryKey) == typeof (long) && Convert.ToInt64((object) this.Id) <= 0L;
    }

    public virtual bool EntityEquals(object obj)
    {
    
      if (obj == null || !(obj is Entity<TPrimaryKey>))
        return false;
      if (this == obj)
        return true;
      Entity<TPrimaryKey> entity = (Entity<TPrimaryKey>) obj;
      if (this.IsTransient() && entity.IsTransient())
        return false;
      Type type1 = this.GetType();
      Type type2 = entity.GetType();
      if (!type1.GetTypeInfo().IsAssignableFrom(type2) && !type2.GetTypeInfo().IsAssignableFrom(type1))
        return false;
      if (this is IMayHaveTenant && entity is IMayHaveTenant)
      {
    
        int? tenantId1 = this.As<IMayHaveTenant>().TenantId;
        int? tenantId2 = entity.As<IMayHaveTenant>().TenantId;
        if (!(tenantId1.GetValueOrDefault() == tenantId2.GetValueOrDefault() & tenantId1.HasValue == tenantId2.HasValue))
          return false;
      }
      return (!(this is IMustHaveTenant) || !(entity is IMustHaveTenant) || this.As<IMustHaveTenant>().TenantId == entity.As<IMustHaveTenant>().TenantId) && this.Id.Equals((object) entity.Id);
    }

    public override string ToString() => string.Format("[{0} {1}]", (object) this.GetType().Name, (object) this.Id);
 }

Interface agreement

In many applications , Many entities have properties like CreationTime Properties of ( The database table also has this field ) Used to indicate when the entity was created .APB Some useful interfaces are provided to implement these similar functions . in other words , For these entities that implement these interfaces , It provides a general coding method ( Generally speaking, as long as the specified interface is implemented, the specified function can be realized ), With ABPUser For example , As shown below AbpUser class , Inherits many default implementation interfaces .

  • IAudited Audit
  • ISoftDelete Soft delete
  • IPassivable Activate / Idle state

public abstract class AbpUser<TUser> : AbpUserBase, IFullAudited<TUser>, 
      IAudited<TUser>, IAudited, ICreationAudited, IHasCreationTime, 
      IModificationAudited, IHasModificationTime, ICreationAudited<TUser>, 
      IModificationAudited<TUser>, IFullAudited, IDeletionAudited, 
      IHasDeletionTime, ISoftDelete, IDeletionAudited<TUser>
    where TUser : AbpUser<TUser>

ABP Domain layer —— Storage (Repositories)

ABP Frame combat series ( One )- Introduction to persistence layer There is an explanation of the concept of warehousing in ,( Recommend the official account , Dry cargo is full. )

ad locum , Let's go into the warehousing section , stay ABP in , Storage class to achieve IRepository Interface . The best way is to define different interfaces for different storage objects . For storage IRepository Defines many generic methods . such as :Select、Insert、Update、Delete Method (CRUD operation ). Most of the time ,, These methods are sufficient to meet the needs of general entities . If these parties are sufficient for the entity , We no longer need to create the warehousing interface required by this entity / class . That is to say Custom warehousing .

IRepository The common methods of retrieving entities from database are defined . stay ABP In the frame AbpRepositoryBase Derived IRepository Interface

  public abstract class AbpRepositoryBase<TEntity, TPrimaryKey> : IRepository<TEntity, TPrimaryKey>, IRepository, ITransientDependency, IUnitOfWorkManagerAccessor
    where TEntity : class, IEntity<TPrimaryKey>
 

Interface methods mainly include :Query、GetAll、Single、Insert、Update、Delete、Count And so on .

  GetAll():IQueryable<TEntity>
  GetAllIncluding(params Expression<Func<TEntity,object>>[] propertySelectors):IQueryable<TEntity>
  GetAllList():List<TEntity>
  GetAllListAsync():Task<List<TEntity>>
  GetAllList(Expression<Func<TEntity,bool>> predicate):List<TEntity>
  GetAllListAsync(Expression<Func<TEntity,bool>> predicate):Task<List<TEntity>>
  Query<T>(Func<IQueryable<TEntity>,T> queryMethod):T
  Get(TPrimaryKey id):TEntity
  GetAsync(TPrimaryKey id):Task<TEntity>
  Single(Expression<Func<TEntity,bool>> predicate):TEntity
  SingleAsync(Expression<Func<TEntity,bool>> predicate):Task<TEntity>
  FirstOrDefault(TPrimaryKey id):TEntity
  FirstOrDefaultAsync(TPrimaryKey id):Task<TEntity>
  FirstOrDefault(Expression<Func<TEntity,bool>> predicate):TEntity
  FirstOrDefaultAsync(Expression<Func<TEntity,bool>> predicate):Task<TEntity>
  Load(TPrimaryKey id):TEntity
  Insert(TEntity entity):TEntity
  InsertAsync(TEntity entity):Task<TEntity>
  InsertAndGetId(TEntity entity):TPrimaryKey
  InsertAndGetIdAsync(TEntity entity):Task<TPrimaryKey>
  InsertOrUpdate(TEntity entity):TEntity
  InsertOrUpdateAsync(TEntity entity):Task<TEntity>
  InsertOrUpdateAndGetId(TEntity entity):TPrimaryKey
  InsertOrUpdateAndGetIdAsync(TEntity entity):Task<TPrimaryKey>
  Update(TEntity entity):TEntity
  UpdateAsync(TEntity entity):Task<TEntity>
  Update(TPrimaryKey id, Action<TEntity> updateAction):TEntity
  UpdateAsync(TPrimaryKey id, Func<TEntity,Task> updateAction):Task<TEntity>
  Delete(TEntity entity):void
  DeleteAsync(TEntity entity):Task
  Delete(TPrimaryKey id):void
  DeleteAsync(TPrimaryKey id):Task
  Delete(Expression<Func<TEntity,bool>> predicate):void
  DeleteAsync(Expression<Func<TEntity,bool>> predicate):Task
  Count():int
  CountAsync():Task<int>
  Count(Expression<Func<TEntity,bool>> predicate):int
  CountAsync(Expression<Func<TEntity,bool>> predicate):Task<int>
  LongCount():long
  LongCountAsync():Task<long>
  LongCount(Expression<Func<TEntity,bool>> predicate):long
  LongCountAsync(Expression<Func<TEntity,bool>> predicate):Task<long>

stay ABP In the frame , If it's directly in NuGet In the installation of ABP Or downloaded from the official website ABP Templates , Rarely seen IRepository Related codes , See the application , Basically included in EntityFrameworkCore Class library , The basic format of user-defined warehouse is given by default .

 public abstract class TestProjectRepositoryBase<TEntity, TPrimaryKey> : EfCoreRepositoryBase<TestProjectDbContext, TEntity, TPrimaryKey>
        where TEntity : class, IEntity<TPrimaryKey>
    {
    
        protected TestProjectRepositoryBase(IDbContextProvider<TestProjectDbContext> dbContextProvider)
            : base(dbContextProvider)
        {
    


            
        }

        // Add your common methods for all repositories
    }

    /// <summary>
    /// Base class for custom repositories of the application.
    /// This is a shortcut of <see cref="TestProjectRepositoryBase{TEntity,TPrimaryKey}"/> for <see cref="int"/> primary key.
    /// </summary>
    /// <typeparam name="TEntity">Entity type</typeparam>
    public abstract class TestProjectRepositoryBase<TEntity> : TestProjectRepositoryBase<TEntity, int>, IRepository<TEntity>
        where TEntity : class, IEntity<int>
    {
    
        protected TestProjectRepositoryBase(IDbContextProvider<TestProjectDbContext> dbContextProvider)
            : base(dbContextProvider)
        {
    

         
        }

        // Do not add any method here, add to the class above (since this inherits it)!!!
    }


Realization of warehousing
ABP It is designed without specifying specific ORM Framework or other means of accessing database technology . As long as the implementation IRepository Interface , Any framework can use .

Storage should use NHibernate or EF It's easy to realize , When you use NHibernate or EntityFramework, If the method provided is sufficient to use , You don't need to create storage objects for your entities . We can inject directly IRepository( or IRepository<TEntity, TPrimaryKey>).

Manage database connections
Opening and closing of database connection , In the storage method ,ABP Automatic connection management .

When the warehousing method is called , The database connection will automatically open and start the transaction . When the warehousing method execution ends and returns , All physical changes are stored , The transaction is committed and the database connection is closed , Everything is done by ABP Automatic control . If the warehousing method throws any type of exception , The transaction will be rolled back automatically and the data connection will be closed . All the above operations are implemented in IRepository All exposed methods of the warehouse class of the interface can be called .

If the warehousing method calls another warehousing method ( Even different storage methods ), They share the same connection and transaction . The connection will be managed by the warehouse method at the top of the warehouse method call chain . More about database management , See UnitOfWork file .

The life cycle of warehousing
All storage objects are temporary . That is to say , They are created when needed .ABP Extensive use of dependency injection , When the warehouse class needs to be injected , New class instances are created automatically by the injection container .

Best practices for warehousing
For one T Type of entity , Yes, you can use IRepository. But don't create customized warehouses under any circumstances , Unless we really need . Predefined warehousing methods are sufficient for various cases .
If you are creating a custom warehouse ( Can achieve IRepository)
The storage class should be stateless . It means , You should not define the state object of storage level, and the call of storage method should not affect other calls .    
When warehousing can use phase based injection , As little as possible or not based on other services .

ABP Domain layer —— The unit of work (Unit Of work)

  • Common connection and transaction management method connection and transaction management is one of the most important concepts for applications using databases , In the application , There are two common ways to create / Release a database connection :
    • A long connection
    • Short connection
  • ABP Connection and transaction management
    • ABP Provide long connection 、 Short connection model ,Repository Is the class of database operation , In execution Repository Methods in the library ,ABP Opened a database connection , And when entering the warehouse method , Will enable a transaction , therefore , You can safely use the methods connected to the warehouse , If an exception occurs during method execution , The transaction is rolled back and the connection is released . In this mode , The storage method is unitary (Unit of work). Check the source code section :
  public class EfCoreRepositoryBase<TDbContext, TEntity> :
      EfCoreRepositoryBase<TDbContext, TEntity, int>, IRepository<TEntity>, 
      IRepository<TEntity, int>, IRepository, ITransientDependency
    where TDbContext : DbContext
    where TEntity : class, IEntity<int>
  {
    
    public EfCoreRepositoryBase(IDbContextProvider<TDbContext> dbContextProvider)
      : base(dbContextProvider)
    {
    
    }

  • The unit of work

    The key word of the unit of work is (Unit of work), Provide transaction services for warehousing , It can be used in two ways

    • Add... To the method [UnitOfWorkAttribute] Ensure transaction consistency

    • Use in method weight IUnitOfWorkManager.Begin(…)、IUnitOfWorkManager.Begin(…)、xxunitOfWork.Complete() Ensure transaction consistency .

      ABP The framework recommends UnitOfWorkattribute The method of adding attributes to the method body .

    • Disable unit of work (Disabling unit of work)

      The method body is set as [UnitOfWork(IsDisabled = true)]

    • Unit of work without transactions (Non-transactional unit of work):[UnitOfWork(false)]

    • Work units call other work units (A unit of work method calls another)

      • Unit of work method ( One is pasted UnitOfWork Method of attribute tag ) Call another unit of work method , They share the same connection and transaction
      • If you create a different thread / Mission , It uses its own unit of work
    • Automated saving changes (Automatically saving changes)

      When we use units of work on methods ,ABP Automatically store all changes at the end of the method .

  • Options :IsolationLevel、Timeout And so on , It can be modified by configuration or initialization assignment

  • Method : Unit of work systems operate seamlessly and invisibly . however , In some special cases , You need to call its methods .ABP Store all changes at the end of the work unit , You don't have to do anything . however , Sometimes , You might want to store all the changes during the unit of work . You can inject IUnitOfWorkManager And call IUnitOfWorkManager.Current.SaveChanges() Method , You can finish saving .

  • event : The unit of work has Completed/Failed/Disposed event

ABP Domain layer —— Data filters (Data filters)

In database development , We usually use soft deletion (soft-delete) Pattern , That is, do not directly delete data from the database , Instead, mark the data as deleted . therefore , If the entity is soft deleted , Then it should not be retrieved in the application . To achieve this effect , We need to add... To the query statement each time we retrieve entities SQL Of Where Conditions IsDeleted = false. It's a boring job , But it is an easy thing to forget . therefore , We should have an automatic mechanism to deal with these problems .

ABP Provide data filters (Data filters), It uses automated , Rule based filtering query .ABP There are already some predefined filters , Of course, you can also create your own filter .

  • Filter
  • Predefined filters
    • Soft delete interface (ISoftDelete)
    • Multi tenancy interface (IMustHaveTenant)
    • Multi tenancy interface (IMayHaveTenant)
  • Disable filters

It can be in the unit of work (unit of work) Call in DisableFilter Method to disable a filter

var people1 = _personRepository.GetAllList();
using(_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete)) {
    
      var people2 = _personRepository.GetAllList();
   }

var people3 = _personRepository.GetAllList();
  • Enable filter

    In the unit of work (unit of work) Use in EnableFilter Method to enable the filter , Like DisableFilter The method is general ( Both are positive and negative to each other )

  • Set filter parameters

Filters can be parameterized (parametric).IMustHaveTenant The filter is a model of this kind of filter , Because the current tenant (tenant) Of Id It was decided during the implementation period . For these filters , If there is a need , We can change the value of the filter

CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42);
  • Custom filter

  • Other object relationship mapping tools

    ABP The data filter only implements Entity Framework On . For others ORM Tools are not yet available .

ABP Domain layer —— Field events (Domain events)

stay C# in , A class can define its own event and other classes can register the event and listen , You can get event notification when an event is triggered . This is for desktop applications or standalone Windows Service It's very useful . however , about Web There will be some problems for the application , Because the object is requested (request) Are created and their lifecycles are short . It is difficult for us to register events of other categories . similarly , Registering events of other classes directly also creates coupling between classes .

In application system , Domain events are used to decouple and reuse (re-use) Business logic .

Event bus

The event bus is a unit (singleton) The object of , It is shared by all other classes , It can trigger and handle events

Get the default instance

EventBus.Default.Trigger(…); // Triggering event

Inject IEventBus Event interface (Injecting IEventBus)

public class TaskAppService : ApplicaService {
    
     public IEventBus EventBus {
     get; set; }
     public TaskAppService() {
    
        EventBus = NullEventBus.Instance;
     }
  }

Event parameters are inherited from EventData class , Source code is as follows

    [Serializable]
    public abstract class EventData : IEventData
    {
    
        /// <summary>
        /// The time when the event occurred.
        /// </summary>
        public DateTime EventTime {
     get; set; }

        /// <summary>
        /// The object which triggers the event (optional).
        /// </summary>
        public object EventSource {
     get; set; }

        /// <summary>
        /// Constructor.
        /// </summary>
        protected EventData()
        {
    
            EventTime = Clock.Now;
        }
    }

Defining events

ABP Definition AbpHandledExceptionData Event and automatically trigger this event when an exception occurs . This is especially useful when you want to get more information about exceptions ( Even if ABP All exceptions have been automatically logged ). You can register this event and set its trigger time when the exception occurs .

ABP It also provides many common event data classes for entity changes : EntityCreatedEventData, EntityUpdatedEventData and EntityDeletedEventData. They are defined in Abp.Events.Bus.Entitis In the namespace . When an entity is added / to update / After deleting , These events will be handled by ABP Automatically trigger . If you have one Person Entity , Can be registered to EntityCreatedEventData, The event will be in the new Person The entity is triggered after it is created and inserted into the database . These events also support inheritance . If Student Class inherits from Person class , And you are registered to EntityCreatedEventData in , Then you will be in Person or Student Trigger received after adding .

Triggering event

public class TaskAppService : ApplicationService {
    
     public IEventBus EventBus {
     get; set; }
     public TaskAppService() {
    
        EventBus = NullEventBus.Instance;
     }

     public void CompleteTask(CompleteTaskInput input) {
    
        //TODO:  Completed task on database 
        EventBus.Trigger(new TaskCompletedEventData {
     TaskId = 42 } );
     }
  }

Event handling

To handle events , You should achieve IEventHandler The interface is shown below

public class ActivityWriter : IEventHandler<EventData>, ITransientDependency {
    
     public void HandleEvent(EventData eventData) {
    
        WriteActivity("A task is completed by id = " + eventData.TaskId);
     }
  } 

Handling multiple events (Handling multiple events)

 public class ActivityWriter :
     IEventHandler<TaskCompletedEventData>,
     IEventHandler<TaskCreatedEventData>,
     ITransientDependency
  {
     public void HandleEvent(TaskCompletedEventData eventData) {
        //TODO:  Handling events 
     }
     public void HandleEvent(TaskCreatedEventData eventData) {
        //TODO:  Handling events 
     }
  }

Register processor

  • Automatic Automatically
    ABP The framework scans all inherited IEventHandler Interface implementation class of , And register in the event bus , When the event occurs , Can pass DI To instantiate an object , And call the event method .
  • Manual type (Manually)
EventBus.Register<TaskCompletedEventData>(eventData =>
{
    
   WriteActivity("A task is completed by id = " + eventData.TaskId);
});

Unregister event

// Register an event

EventBus.Unregister<TaskCompletedEventData>(handler);

Blogger GitHub Address
https://github.com/yuyue5945

Pay attention to the official account

  • Automatic Automatically
    ABP The framework scans all inherited IEventHandler Interface implementation class of , And register in the event bus , When the event occurs , Can pass DI To instantiate an object , And call the event method .
  • Manual type (Manually)
EventBus.Register<TaskCompletedEventData>(eventData =>
{
    
   WriteActivity("A task is completed by id = " + eventData.TaskId);
});

Unregister event

// Register an event

EventBus.Unregister<TaskCompletedEventData>(handler);

Blogger GitHub Address
https://github.com/yuyue5945

Pay attention to the official account

 official account

原网站

版权声明
本文为[yuyue5945]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202180546225028.html