Designing entities
The entities are classes written by hand in C#, using a set of base classes, attributes and collection that serve us as standard building blocks.
Entity
: Base class to create new entity types
EmbeddedEntity
: Base class to create small entities that will be stored in-line in the parent entity. Similar to database value-types.
Lite<T>
: It is a type-safe identity card for an entity. Stores the Id
and ToString
and it is the only mechanism for lazy-loading. Allows inheritance using covariance. Massively usefull (Combobox items, Autocomplete, Search control, many APIs,...).
MList<T>
: Alternative to List<T> for entity properties. Keeps track of changes and optionally preserves ordering in the database. T
can be any type (e.g., entities, embedded entities, Lite<T>, or values).
- Values: Any simple data type that can be part of an entity property (e.g., strings, numbers, nullable values, enums, date, times and any UDT like geographic data).
This standards simplify your code dramatically, providing good default behaviors in the user interface, allowing generic programming in the business logic, and ensuring easy integration and good performance at the database schema.
Additionally, the entities type and properties are localizable. This way the consistency in the user interface is guaranteed and there are less messages to translate, sharing a common language with non-English speaking users.
Entities change
Entities play a central role in any application built with Signum Framework: database schema, business logic, queries, windows and web user interfaces, tests and even documentation depend on the way the entities have been designed.
Unfortunately... the entities are the ones that suffer more changes. When a new property is created, removed or renamed, it will affect all the dependent layers and there’s no dependency injection mechanism that can protect you from that.
By using statically typed code, and schema and documentation synchronization, we can absorb this impacts and let the entities change if necessary, keeping the artificial mismatch between what the users sees and how the information is structured to a minimum.
Powerful Validation
One of the main responsibilities of the entities is defining their validation rules. This rules will be checked before saving the entity, and the same rules will also be shown in the windows or web user interface, removing redundancy.
Signum Framework provides a wide range of options available to define this rules:
- Simple rules using attributes over the properties.
- More complicated rules using imperative C# code (with or without database access).
- Tables to define mandatory properties depending on the entity state.
- ...or even inject contextual validation rules to child entities.
All this rules can be overridden if the module is not written by you, and there’s support to disable any rule on entities loaded from legacy applications.
Embedded Change Tracking
Additionally, all the entities have a built-in change tracking system to commit only the necessary changes when the entity is saved in the database.
This mechanism lies in the entity itself, not in an external context. That means the entities can be safely serialized and sent to remote client applications, and still have change tracking and concurrency control working when they turn back.
Exposing the data
In today application is common that the database schema, the way the information is structured, is hidden for the end-user behind complicated naming conventions, table and column with out-dated names or not used anymore, or user interfaces that, trying to simplify some user case, complicate the overall picture.
While some of this decisions could be justified in some particular cases, in is necessary for power users to understand the data, so they can explore the information, design reports and, ultimately, share a common languages with the developers.
Signum Framework removes the technical impediments that keep the data hidden to the world, but gives you the security mechanisms to protect this data when necessary.
Operations
Types of Operations
Operations are actions to create and manipulate entities, and are available in the user interface. There are five types of operations:
Construct
: Create a new entity with no additional context (e.g., Create new Invoice)
ConstructFrom
: Create a new entity from another one (e.g., Create Invoice from Customer)
ConstructFromMany
: Create a new entity from many others (e.g., Create Invoice selecting Products)
Execute
: Modify an entity (e.g., Authorize Invoice, Cancel Invoice)
Delete
: Delete an entity from the database (e.g., Delete Order)
Most operations contain, not only the code that will run when executed, but also code to check the operation pre-conditions (CanExecute
), returning an error message if not.
Additionally, operations can be embedded in a state machine, where each operation represents a transition between the states of the entity.
Advantages
While operations could be manually implemented using manual UI buttons/commands and business logic, they have important advantages that make them a more convenient way to creating and manipulate entities:
- Operations are defined in the Server, but create automatic buttons in the entity UI (or context menu on the search dialog) for Windows and Web applications.
- When the pre-condition is not satisfied by the entity, the button (or context menu) is disabled, and a useful tooltip with the message is shown to the end-user.
- Operation store a log every time they are executed, with the start and end dates, user, related entity, and maybe an exception.
- They can be overridden if special behavior is necessary in complex class hierarchies.
- Their implementation can also be replaced, making each operation an extensibility point.
- They all can share a common set of Web Service Operations (Windows) or Controller Actions (Web) saving you code.
- They are automatically transactional.
- When using Authorization module, they can easily be allowed / disallowed for certain roles.
- When using Processes module, they can easily be executed for multiple entities at once, using a context menu in the search dialog.
Mixins & Symbols
Once you start including in your database entities designed in third-party modules new challenges appear. We have created some new patterns to solve this challenges.
Mixins and Symbols let us push code-reuse to the limits, without scarifying a static-typed programming experience.
Mixins
In statically-typed languages like C# is not possible to add extra members to type without inheriting from it (creating a new, different type). Extensions method can add new functions, but adding new fields/properties (and database columns) requires changes in the memory layout of the entity, and there is no standard alternative to do it.
Using MixinEntity
we have an standart mechanism to add new fields, properties or methods to any entity. Mixins are just like a EmbeddedEntity
attached to the end of an entity.
When an entity is created, all the registered mixins for this entity type are attached to the end of it, and there is no way to remove them. Mixins are like limpets, so you can trust that your entity will have the new properties defined in the mixin.
Attached mixins have no name, and are accessible by type. Entities can have many mixins, but not two mixin of the same type.
Mixins are fully supported at all levels of the framework (e.g., ORM, LINQ, Window/Web user interface, Dynamic queries, Authorization ...) and their properties are indistinguishable from normal properties for the end-user.
There are extension points to modify third-party operations and (windows and web) user interfaces to make use of the new fields provided by the mixin.
Symbols
Enums are a common data-type to define a fixed amount of different values. Under the cover they are just numbers, but with a special superpower: Calling ToString
on an enum value returns the name of the field where is was declared, not the underlying int. Enum values always remember their type and the field where they were declared.
Unfortunately, enums are not very flexible : There’s no way to add new values to an enum already declared in a third-party library. When this is necessary, the typical solution is to use magic strings instead of enums. But strings are not strongly typed: no auto-completion, no rename, no compile-time errors…
Symbols solve this problems. You can declare a new symbol type just by inheriting from Symbol, but you can declare new symbol fields/instances in any static class. Moreover, they inherit their name and type from the field where they were declared, just like enums.
Also, symbols are entities, so can be reference by other entities easily and with referential integrity. Moreover, the schema synchronizer understand and synchronizes the used symbols.
This features make symbols a replacement for magic strings that are as easy to use as enums and can be referenced in the database. They are simple read-only entities that are usually related to a piece of code in a dictionary, creating a strongly-typed bridge between code and data. (e.g.,Operations, Permissions, Process algorithms, etc... are all symbols) .