5.5 KiB
Organization Unit Management
Organization units (OU) is a part of Identity Module and can be used to hierarchically group users and entities.
OrganizationUnit Entity
An OU is represented by the OrganizationUnit entity. The fundamental properties of this entity are:
- TenantId: Tenant's Id of this OU. Can be null for host OUs.
- ParentId: Parent OU's Id. Can be null if this is a root OU.
- Code: A hierarchical string code that is unique for a tenant.
- DisplayName: Shown name of the OU.
The OrganizationUnit entity's primary key (Id) is a Guid type and it derives from the FullAuditedAggregateRoot class which provides audit information with IsDeleted property (OUs are not deleted from the database, they are just marked as deleted).
Organization Tree
Since an OU can have a parent, all OUs of a tenant are in a tree structure. There are some rules for this tree;
- There can be more than one root (where the ParentId is null).
- Maximum depth of the tree is defined as a constant as OrganizationUnit.MaxDepth. The value is 16.
- There is a limit for the first-level children count of an OU (because of the fixed OU Code unit length explained below).
OU Code
OU code is automatically generated and maintained by the OrganizationUnit Manager. It's a string that looks something like this:
"00001.00042.00005"
This code can be used to easily query the database for all the children of an OU (recursively). There are some rules for this code:
- It must be unique for a tenant.
- All the children of the same OU have codes that start with the parent OU's code.
- It's fixed length and based on the level of the OU in the tree, as shown in the sample.
- While the OU code is unique, it can be changeable if you move an OU.
- We must reference an OU by Id, not Code.
OrganizationUnit Manager
The OrganizationUnitManager class can be injected and used to manage OUs. Common use cases are:
- Create, Update or Delete an OU
- Move an OU in the OU tree.
- Getting information about the OU tree and its items.
Multi-Tenancy
The OrganizationUnitManager is designed to work for a single tenant at a time. It works for the current tenant by default.
Common Use Cases
There are some common use cases for OUs. You can find the source code of the samples here.
Creating An Entity That Belongs To An Organization Unit
The most obvious usage of OUs is to assign an entity to an OU. Sample entity:
public class Product : AuditedAggregateRoot<Guid>, IMultiTenant
{
public virtual Guid OrganizationUnitId { get; private set; }
public virtual Guid? TenantId { get; }
public virtual string Name { get; private set; }
public virtual float Price { get; private set; }
}
You need to create OrganizationUnitId property to assign this entity to an OU. Depending on requirement, this property can be nullable. You can now relate a Product to an OU and query the products of a specific OU.
You can use IMultiTenant interface if you want to distinguish products of different tenants in a multi-tenant application (see the Multi-Tenancy document for more info). If your application is not multi-tenant, you don't need this interface and property.
Getting Entities In An Organization Unit
To get the Products of an OU, you can implement a simple domain service; Product Manager in this case to get the filtered data:
public class ProductManager : IDomainService
{
private readonly IProductRepository<Product> _productRepository;
public ProductManager(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public List<Product> GetProductsInOu(OrganizationUnit organizationUnit)
{
return (await _productRepository.GetListAsync()).Where(q => q.OrganizationUnitId == organizationUnit.Id).ToList();
}
}
For better practice, you should consider querying it on domain layer for performance and scalability. To do so, add a method to your repository interface:
public interface IProductRepository : IRepository<Product, Guid>
{
public Task<List<Product>> GetProductsOfOrganizationUnitAsync(Guid organizationUnitId);
}
Then implement it on your ORM layer (which is EntityFrameworkCore in this sample):
public Task<List<Product>> GetProductsOfOrganizationUnitAsync(Guid organizationUnitId)
{
return DbSet.Where(p => p.OrganizationUnitId == organizationUnitId).ToListAsync();
}
Afterwards, you can modify your domain service like below:
public List<Product> GetProductsInOu(OrganizationUnit organizationUnit)
{
return await _productRepository.GetProductsOfOrganizationUnitAsync(organizationUnit.Id);
}
Settings
You can find MaxUserMembershipCount settings under SettingManagement:
- MaxUserMembershipCount: Maximum allowed membership count for a user. Default value is int.MaxValue which allows a user to be a member of unlimited OUs at the same time.