Domain Logic
- 11 minutes to read
Important
Domain Component (DC) interfaces are abstractions above eXpress Persistent Objects (XPO) classes. Notwithstanding its benefits, this abstraction imposes certain technical limitations. DC is only suitable for certain usage scenarios. The Blazor User Interface is not supported for DC. DC is in maintenance mode and we do not recommend its use in new software projects. We recommend that you migrate DC interfaces to pure XPO classes.
Each Domain Component can have one or more associated Domain Logic(s). A Domain Logic is represented by a class decorated with the DomainLogicAttribute, indicating the associated Domain Component interface. When an entity aggregating the Domain Component is instantiated, the associated Domain Logic classes are instantiated as well. Just like a regular class, a Domain Logic declaration can have an instance and static fields, properties and methods. Some of these provide implementations for the Domain Component’s members, while others can be used like regular class members.
Domain Logic Naming Conventions
To provide implementations for Domain Component members, a Domain Logic class must expose methods that follow specific naming conventions.
Method Name | Description | Examples |
---|---|---|
Get_PropertyName | Executed when getting a target property value. A target property should be read-only or non-persistent. Use this method to implement calculated properties. |
|
Set_PropertyName | Executed when setting a target property value. A target property should not be read-only. Use this method to implement non-persistent properties. |
|
BeforeChange_PropertyName | Executed before a target property is changed. A target property should not be read-only. Use this method to preserve data integrity. |
|
AfterChange_PropertyName | Executed after a target property is changed. A target property should not be read-only. Use this method to implement dependent properties. |
|
MethodName | Executed when the target method is called. Use this method to define the target method body. If MethodName is not declared in a corresponding Domain Component interface, the MethodName Domain Logic method is treated like a regular method. You can declare such methods, for instance, to provide functionality required by other Domain Logic methods. |
|
AfterConstruction | Executed after an object is constructed. Use this method instead of the previous one to initialize several properties at once. |
|
OnDeleting | Executed before an object is deleted. Use this method to perform actions before an object has been deleted from a data store. |
|
OnDeleted | Executed after an object is deleted. Use this method to specify the actions that should be performed after an object has been deleted from a data store. |
|
OnSaving | Executed before an object is saved. Use this method to perform actions before saving an object’s state to a data store. |
|
OnSaved | Executed after an object is saved. Use this method to perform actions after saving an object’s state to a data store. |
|
OnLoaded | Executed after an object is loaded from a data store. Use this method to perform actions after loading an object’s state from a data store. |
|
PropertyName and MethodName should be substituted with target member names, for example, to implement the get accessor for a Name property, declare a Get_Name Domain Logic method.
All of the methods mentioned above should be public, can be instance-level or static, and must have one of the following parameter sets.
LogicMethodName(source_parameters)
source_parameters is a variable number of parameters mirroring the parameters declared in the corresponding domain component’s method, or the parameters required by the logic method itself. This Domain Logic method signature does not provide any specific parameters. Generally, the signature is used when you are implementing utility Domain Logic methods.
LogicMethodName(target_interface, source_parameters)
target_interface is an arbitrarily named parameter of the type corresponding to the target Domain Component. When the logic method is called, a reference to the associated entity instance is passed by this parameter. Use this method signature when you need to access the object whose method or property accessor has been invoked. Generally, this method signature is used to read and change the associated object’s property values.
LogicMethodName(target_interface, object_space, source_parameters)
object_space is an arbitrary parameter of the IObjectSpace type. When the logic method is called, a reference to the associated entity instance’s Object Space is passed via this parameter. Use this method signature when you need to perform CRUD (create, read, update, and delete) operations on business objects and entity instances in a Domain Logic method. Object Spaces expose a vast number of object manipulation methods (see Create, Read, Update and Delete Data).
Note
When a domain logic method that mimics the base object’s method is declared, the base method is not called at all. For instance, if you declare the OnSaving domain logic method, the OnSaving method of a base domain component or a base class is not called.
Domain Logic Examples
The following code snippet illustrates a sample PersonLogic Domain Logic class implementation. A Get_FullName Domain Logic method provides implementation for the calculated IPerson.FullName property getter. The FullNameSeparator string constant is used by the implementation. The Copy method provides implementation for the IPerson.Copy method. If the IPerson Domain Component does not declare the Copy method, the PersonLogic.Copy method will be treated like a regular static method to be used in code to correctly copy the IPerson related data.
[DomainLogic(typeof(IPerson))]
public class PersonLogic {
public const string FullNameSeparator = " ";
public string Get_FullName(IPerson person) {
return string.Format("{0}{1}{2}", person.FirstName, FullNameSeparator, person.LastName);
}
public static void Copy(IPerson person, IPerson target) {
if(target != null) {
target.FirstName = person.FirstName;
target.LastName = person.LastName;
}
}
}
When implementing Domain Logic for the setter and getter of an instance property, you can use simplified syntax. The following code snippet illustrates this. The PersonLogic Domain Logic provides implementation of a non-persistent IPerson.FullName property without defining the Get_ and Set_ logic methods. Instead, a FullName property with the same signature is declared in the Domain Logic class. Note the PersonLogic constructor used to receive an instance of the current IPerson object.
[DomainComponent]
public interface IPerson {
string FirstName { get; set; }
[NonPersistentDc]
string FullName { get; set; }
}
[DomainLogic(typeof(IPerson))]
public class PersonLogic {
IPerson person;
public PersonLogic(IPerson person) {
this.person = person;
}
public string FullName {
get { return person.FirstName; }
set { person.FirstName = value; }
}
}
This approach cannot be used to implement static Domain Component properties, except in the case where a static property is calculated and does not depend on the current object instance. The following code snippet illustrates this scenario.
[DomainComponent]
public interface IContact {
static string Name { get; }
}
[DomainLogic(typeof(IContact))]
public class ContactLogic {
public static string Name {
get { return "a constant string"; }
}
}
The following code snippet demonstrates an implementation of the AfterConstruction logic method. The method uses the Domain Logic method signature with an IObjectSpace parameter to create an instance of the entity implementing the IAddress interface, and then uses the instance to initialize the IPerson.Address property.
[DomainLogic(typeof(IPerson))]
public class AdditionalPersonLogic {
public static void AfterConstruction(IPerson person, IObjectSpace objectSpace) {
person.Address = objectSpace.CreateObject<IAddress>();
}
}
If a Domain Component aggregates a Domain Component that has logic methods defined for its members, you can override these methods by creating a new Domain Logic class. The following code snippet illustrates this. The IPerson Domain Component has an associated Domain Logic class that declares the Get_DisplayName method. The IClient Domain Component aggregates IPerson. A Domain Logic class associated with IClient overrides the Get_DisplayName method.
[DomainComponent]
public interface IPerson {
[ImmediatePostData]
string FirstName { get; set; }
[ImmediatePostData]
string LastName { get; set; }
string DisplayName { get; }
}
[DomainLogic(typeof(IPerson))]
public class IPerson_Logic {
public string Get_DisplayName(IPerson person) {
return person.FirstName + " " + person.LastName;
}
}
[DomainComponent]
public interface IClient : IPerson {
[ImmediatePostData]
string ClientID { get; set; }
}
[DomainLogic(typeof(IClient))]
public class IClient_Logic {
public string Get_DisplayName(IClient client) {
return client.ClientID;
}
}
To define a Get_ method for a collection property, you must decorate the property with the NonPersistentDc attribute. This is a required step, because a collection property is treated as persistent by default, so you will not be able to provide Get_ logic for it.
[DomainComponent]
public interface IOrder {
[NonPersistentDc]
IList<IOrderLine> OrderLines { get; }
}
[DomainLogic(typeof(IOrder))]
public class OrderLogic {
public IList<IOrderLine> Get_OrderLines(IOrder order) {
//...
}
}
When a Domain Component aggregates a regular interface, you need to provide the corresponding logic for regular interface members. The following example will not run unless you implement the Get_ logic method for the UserName property and the Get_ , Set_ logic methods for the IsActive property.
public interface IUser {
bool IsActive { get; set; }
string UserName { get; }
}
[DomainComponent]
public interface IPerson : IUser {
string LastName { get; set; }
string FirstName { get; set; }
}
Domain Logic classes declared in assemblies containing Domain Components registered via the ITypesInfo.RegisterEntity method are registered automatically. You can still control the process manually via the ITypesInfo.RegisterDomainLogic and ITypesInfo.UnregisterDomainLogic methods. This is especially useful when you do not have access to Domain Logic class sources, but need to manipulate the Domain Logic assignment.
Domain Logic and Persistent Aliases
You can use CalculatedAttribute or PersistentAliasAttribute to make a non-persistent property calculated (internally, the Calculated attribute is converted to PersistentAlias when generating persistent classes from Domain Components). In this instance, the getter logic is created automatically and the following error occurs if you try to override it with the Domain Logic:
The … logic method(s) will not be used for the … property because this property is persistent or has the PersistentAlias attribute. Either remove the logic method(s) declaration or make the property non-persistent or remove the attribute.
However, you can set the static XpoTypeInfoSource.AllowDomainLogicForCalculatedProperties property to true to allow the Domain Logic and persistent aliases to be used simultaneously.