-
Notifications
You must be signed in to change notification settings - Fork 19
Domain
In the domain layer, there are supertypes to model the domain, such as entities, records, value objects, and enumerations. The domain layer also knows the repository layer supertype, for handling instances of entities and structs.
Using easy, your entities, as described in domain-driven design, inherit from the Entity
class. This gives your entity identity. The default implementation of Entity
provides a generated id
property (it's a UUID by default).
All classes that inherit from Record
or Entity
will have an internal object called state
. Normally, the state of an object is passed to it during construction. Using this internal object state
allows for easy mapping of the content of an entity, which is usually JSON, to its properties. We prefer to keep our entities immutable. Properties therefore can be read-only. An update to an object is considered a state change and should therefore always return a new instance of the entity, instead of modifying the state of the current instance.
An example of an entity is the Movie
class below. Here the content of the object comes from an external service (called OMDB) and is mapped to the actual properties of the Movie
class.
export class Movie extends Entity {
@required() readonly id: Id = this.state.imdbID;
@required() readonly title: string = this.state.Title;
@required() readonly year: number = this.state.Year;
readonly poster: string = this.state.Poster;
update = (add?: Json): Movie => new Movie(this.toJSON(add));
}
Some of the properties of Movie
have decorators, such as @required
. These decorators can be used to validate the object, using the separate validate()
function.
Most modern programming languages support the use of enumerables. The goal of an enumerable is to allow only a limited set of values to be chosen for a particular property or passed as a parameter of a function, or its return type. Although this seems trivial, there are some drawbacks to using enumerables.
First of all, in most languages, you can not inherit from enumerables. As a result, if you define an enumerable in a library, and would like to add values to it in another repository, this is not possible. If you would, as we do in easy support a list of scopes, we could have created an enumerable Scope
, with the scopes we see. However, if you use easy and would like to add your own scopes, this is not possible with a default enumerable.
Secondly, in most languages (Java not included), enumerations only have two properties, the name and the index of its items. If you would want to have some more properties on your enumerables or add some behavior, an enumerable is not your best bet.
And thirdly, and perhaps the most dangerous one, if you persist your enumerables to a storage facility (a database for instance), enumerations are usually stored using their index. This makes the data hard to interpret. After all, what does scope 2
really mean? But even worse, if you would add more items to your enumerable later on, the index of the items might alter, and hence the stored data gets a different meaning, often without noticing.
Therefore, easy provides an Enum
class, which is both extendable and allows you to define meaningful identifiers for your items, and also add additional properties. And still, the behavior of enumerables created using the Enum
class, is still comparable to traditional enumerables. Here's the UseCase
enumerable from easy as an example.
export class UseCase extends Enum {
constructor(readonly scope: Scope, name: string, id: string = stringify(name).kebab) {
super(name, id);
}
static readonly Main = new UseCase(Scope.Basic, "Main");
static readonly Login = new UseCase(Scope.Auth, "Login");
static readonly Logout = new UseCase(Scope.Auth, "Logout");
static readonly ForgotPassword = new UseCase(Scope.Auth, "Forgot password");
static readonly ChangePassword = new UseCase(Scope.Auth, "Change password");
}
The class UseCase
has five items, such as UseCase.Main
or UseCase.ChangePassword
. The constructor has an additional property scope
, which the Enum
class does not have, but it calls on the constructor of its superclass to actually make it work. All instances of enumerables have a property id
, which is used to store the enums when used as a property on entities, or for comparison.