Overview

In this section we run through the main building blocks that make up an Apache Isis application.

Type of Domain Objects

Apache Isis supports recognises four main types of domain classes:

  • domain entities - domain objects persisted to the database the ORM (eg JDO/DataNucleus); for example Customer

  • domain services - generally singletons, automatically injected, and providing various functionality; for example CustomerRepository

  • view models - domain objects that are a projection of some state held by the database, in support a particular use case; for example CustomerDashboard (to pull together commonly accessed information about a customer).

  • mixins - allow functionality to be "contributed" in the UI by one module to another object, similar to traits or extension methods provided in some programming languages. This is an important capability to help keep large applications decoupled.

From the end-user’s perspective the UI displays a single domain object instance that has state (that is, a domain entity or a view model) per page. The end-user can then inspect and modify its state, and navigate to related objects.

Domain classes are generally recognized using annotations. Apache Isis defines its own set of annotations, and you can generally recognize an Apache Isis domain class because it will be probably be annotated using @DomainObject and @DomainService.

The framework also recognises some annotations from the ORM layer (eg JDO/DataNucleus) and infers domain semantics from these annotations. Similarly, JAXB annotations are typically used for view models.

The framework also defines supplementary annotations, notably @DomainObjectLayout and @DomainServiceLayout. These provide hints relating to the layout of the domain object in the user interface. Alternatively, these UI hints might be defined in a supplementary .layout.xml file.

Domain Entities

Most domain objects that the end-user interacts with are likely to be domain entities, such as Customer, Order, Product and so on. These are persistent objects and which are mapped to a database (usually relational), using JDO/DataNucleus annotations.

Some domain entities are really aggregates, a combination of multiple objects. A commonly cited example of this is an Order, which really consists of both a root Order entity and a collection of OrderItems. From the end-users' perspective, when they talk of "order" they almost always mean the aggregate rather than just the Order root entity.

Eric Evans' Domain Driven Design has a lot to say about aggregate roots and their responsibilities: in particular that it is the responsibility of the aggregate root to maintain the invariants of its component pieces, and that roots may only reference other roots. There’s good logic here: requiring only root-to-root relationships reduces the number of moving parts that the developer has to think about.

On the other hand, this constraint can substantially complicate matters when mapping domain layer to the persistence layer. DDD tends to de-emphasise such matters: it aims to be completely agnostic about the persistence layer, with the responsibilities for managing relationships moved (pretty much by definition) into the domain layer.

As a framework Apache Isis is less dogmatic about such things. Generally the domain objects are mapped to a relational database and so we can lean on the referential integrity capabilities of the persistence layer to maintain referential invariants. Said another way: we don’t tend to require that only roots can maintain roots: we don’t see anything wrong in an InvoiceItem referencing an OrderItem, for example.

Nonetheless the concepts of "aggregate" and "aggregate root" are worth holding onto. You’ll probably find yourself defining a repository service (discussed in more detail below) for each aggregate root: for example Order will have a corresponding OrderRepository service. Similarly, you may also have a factory service, for example OrderFactory. However, you are less likely to have a repository service for the parts of an aggregate root: the role of retrieving OrderItems should fall to the Order root (typically by way of lazy loading of an "items" collection) rather than through an OrderItemRepository service. This isn’t a hard-n-fast rule, but it is a good rule of thumb.

Details on how to actually write a domain entity (the programming model for domain entities) is here.

Domain Services

Domain services are (usually) singleton stateless services that provide additional functionality. Domain services consist of a set of logically grouped actions, and as such follow the same conventions as for entities. However, a service cannot have (persisted) properties, nor can it have (persisted) collections.

A very common type of domain service is a repository, that is used to look up existing instances of a domain entity. For example, for the Customer entity there may be a CustomerRepository, while for Order entity there may be an OrderRepository.

Similarly, entities might also have a corresponding factory service: a CustomerFactory or an OrderFactory; Evans' Domain Driven Design, draws a clear distinction between a factory (that creates object) and a repository (that is used to find existing objecpts).

On the other hand, from an end-users' perspective the act of finding an existing object vs creating a new one are quite closely related. For this reason, in Apache Isis it’s therefore quite common to have a single domain service that acts as both a factory and a repository (and is usually called just a "repository").

The behaviour of these services is rendered in various ways, though the most obvious is as the menu actions on the top-level menu bars in the Wicket viewer's UI.

Domain services can also be used for a number of other purposes:

  • to provide additional non-UI functionality; an example being to perform an address geocoding lookup against the google-maps API, or to perform some calculation, or attach a barcode, send an email etc

  • to act as a subscribers to the event bus, potentially influencing events fired by some other module (a key technique for decoupling large applications)
    + This is discussed in more detail below, in the section on events.

  • to implement an SPI of the Apache Isis framework, most notably cross-cutting concerns such as security, command profiling, auditing and publishing.

Domain objects of any type (entities, other services, view models, mixins) can also delegate to domain services; domain services are automatically injected into every other domain object. This injection of domain services into entities is significant: it allows business logic to be implemented in the domain entities, rather than have it "leach away" into supporting service layers. Said another way: it is the means by which Apache Isis helps you avoid the anaemic domain model anti-pattern.

Domain services are instantiated once and once only by the framework, and are used to centralize any domain logic that does not logically belong in a domain entity or value.

Details on how to actually write a domain service (the programming model for domain services) is here.

Hexagonal Architecture

It’s worth extending the Hexagonal Architecture to show where domain services fit in:

hexagonal architecture addons
Figure 1. The hexagonal architecture with API and SPI implementations

The extensions catalog provide SPI implementations of the common cross-cutting concerns. You can also write your own domain services as well, for example to interface with some external CMS system, say.

View Models

View models are similar to entities in that (unlike domain services) there can be many instances of any given type. End users interact with view models in the same way as a domain entity, indeed they are unlikely to distinguish one from the other.

However, whereas domain entities are mapped to a datastore, view models are not. Instead they are recreated dynamically by serializing their state, ultimately into the URL itself (meaning their state it is in effect implicitly managed by the client browser). You will notice that the URL for view models (as shown in Wicket viewer or RestfulObjects viewer) tends to be quite long.

This capability opens up a number of more advanced use cases:

  • In the same way that an (RDBMS) database view can aggregate and abstract from multiple underlying database tables, a view model sits on top of one or many underlying entities.

  • A view model could also be used as a proxy for some externally managed entity, accessed over a web service or REST API; it could even be a representation of state held in-memory (such as user preferences, for example).

  • view models can also be used to support a particular use case. An example that comes to mind is to expose a list of scanned PDFs to be processed as an "intray", showing the list of PDFs on one side of the page, and the current PDF being viewed on the other. Such view models are part of the application layer, not part of the domain layer (where entities live).

We explore these use cases in more detail below.

Details on how to actually write a view model (the programming model for view models) can be found here.

Externally-managed entities

Sometimes the entities that make up your application are persisted not in the local database but reside in some other system, for example accessible only through a SOAP web service. Logically that data might still be considered a domain entity and we might want to associate behaviour with it, however it cannot be modelled as a domain entity if only because JDO/DataNucleus doesn’t know about the entity nor how to retrieve or update it.

There are a couple of ways around this: we could either replicate the data somehow from the external system into the Isis-managed database (in which case it is once again just another domain entity), or we could set up a stub/proxy for the externally managed entity. This proxy would hold the reference to the externally-managed domain entity (eg an external id), as well as the "smarts" to know how to interact with that entity (by making SOAP web service calls etc).

The stub/proxy is a type of view model: a view — if you like — onto the domain entity managed by the external system.

In-memory entities

As a variation on the above, sometimes there are domain objects that are, conceptually at least entities, but whose state is not actually persisted anywhere, merely held in-memory (eg in a hash).

A simple example is read-only configuration data that is read from a config file (eg log4j appender definitions) but thereafter is presented in the UI just like any other entity.

Application-layer view models

Domain entities (whether locally persisted or managed externally) are the bread-and-butter of Apache Isis applications: the focus after all, should be on the business domain concepts and ensuring that they are solid. Generally those domain entities will make sense to the business domain experts: they form the ubiquitous language of the domain. These domain entities are part of the domain layer.

When developing an Apache Isis application you will most likely start off with the persistent domain entities: Customer, Order, Product, and so on. For some applications this may well suffice.

That said, it may not always be practical to expect end-users of the application to interact solely with those domain entities. If the application needs to integrate with other systems, or if the application needs to support reasonably complex business processes, then you may need to look beyond just domain entities; view models are the tool of choice.

One such use case for view models is to help co-ordinate complex business processes; for example to perform a quarterly invoicing run, or to upload annual interest rates from an Excel spreadsheet, or prepare payment batches from incoming invoices, to be uploaded to an external payment system. In these cases the view model managing the business process might have some state of its own, but in most cases that state does not need to be persisted between user sessions. Many of the actions will be queries but in some cases such view model actions might also modify state of underlying domain entities. Either way, ultimately these actions just delegate down to the domain-layer.

Desire Lines

One way to think of application view models is that they model the "desire line": the commonly-trod path that end-users must follow to get from point A to point B as quickly as possible.

To explain: there are documented examples that architects of university campus will only add in paths some while after the campus buildings are complete: let the pedestrians figure out the routes they want to take. One name for this idea is "desire lines".

What that means is you should add view models after having built up the domain layer, rather than before. These view models pave that commonly-trod path, automating the steps that the end-user would otherwise have to do by hand.

However, you shouldn’t try to build out a domain layer that could support every conceivable use case before starting to think about view models. Instead, iterate. Identify the use case/story/end-user objective that will deliver value to the business. Build out the minimum domain entities to support that use case. Then, introduce view models to simplify high-volume end-user interactions with the system (perhaps automating several related use cases together).

Another common requirement is to show a dashboard of the most significant data in the system to a user, often pulling in and aggregating information from multiple points of the app. Obtaining this information by hand (by querying the respective services/repositories) would be tedious and slow; far better to have a dashboard do the job for the end user.

A dashboard object is a model of the most relevant state to the end-user, in other words it is (quite literally) a view model. It is not a persisted entity, instead it belongs to the application layer.

DTOs

DTOs (data transfer objects) are simple classes that (according to wikipedia) "carry data between processes".

If those two processes are parts of the same overall application (the same team builds and deploys both server and client) then there’s generally no need to define a DTO; just access the entities using Apache Isis' RestfulObjects viewer.

On the other hand, if the client consuming the DTO is a different application — by which we mean developed/deployed by a different (possible third-party) team — then the DTOs act as a formal contract between the provider and the consumer. In such cases, exposing domain entities over RestfulObjects would be "A Bad Thing"™ because the consumer would in effect have access to implementation details that could then not be easily changed by the producer. There’s plenty of discussion on this topic (eg here and here). Almost all of these recommend exposing only DTOs (which is to say view models), not domain entities, in REST APIs.

To support this use case, a view model can be defined such that it can act as a DTO. This is done by annotating the class using JAXB annotations; this allows the consumer to obtain the DTO in XML format along with a corresponding XSD schema describing the structure of that XML.

These DTOs are still usable as "regular" view models; they will render in the Wicket viewer just like any other. In fact (as the programming model section below makes clear), these JAXB-annotated view models are in many regards the most powerful of all the alternative ways of writing view models.

It’s also worth noting that it is also possible to download the XML (or XSD) straight from the UI, useful during development. The view model simply needs to implement the Dto marker interface; the framework has mixins that contribute the download actions to the view model.

For REST Clients

The Restful Objects viewer automatically provides a REST API for both domain entities. Or, you can use it to only expose view models, taking care to map the state of the domain entity/ies into a view model. The question to consider is whether the REST API is a public API or an internal private API:

  • If it’s a public API, which is to say that there are third-party clients out over which you have no control, then view models are the way to go.

    In this case view models provide an isolation layer which allow you to modify the structure of the underlying domain entities without breaking this API.

  • If it’s a private API, which is to say that the only clients of the REST API are under your control, then view models are an unnecessary overhead.

    In this case, just expose domain entities directly.

The caveat to the "private API" option is that private APIs have a habit of becoming public APIs. Even if the REST API is only exposed within your organisation’s intranet, other teams may "discover" your REST API and start writing applications that consume it. If that REST API is exposing domain entities, you could easily break those other teams' clients if you refactor.

The Spring Data REST subproject has a similar capability of being able to expose domain entities as REST resources. This SO question, which debates the pros-and-cons, is also worth a read.

If your REST API is intended to be public (or you can’t be sure that it will remain private), then exposing view models will entail a lot of marshalling of state from domain entities into view models. There are numerous open source tools that can help with that, for example Model Mapper, Dozer and Orika.

Or, rather than marshalling state, the view model could hold a reference to the underlying domain entity/ies and dynamically read from it (ie, all the view model’s properties are derived from the entity’s).

A third option is to define an RDBMS view, and then map a "non-durable" entity to that view. The RDBMS view then becomes the public API that must be preserved. ORMs such as DataNucleus support this.

Mixins

The final type of domain object is the mixin. These are similar to traits or extension methods in other programming languages. A mixin object allows one class to contribute behaviour - actions, (derived) properties and (derived) collections - to another domain object, either a domain entity or view model. Or rather, the mixin appears to contribute the behaviour/state in the UI; the underlying domain class being "decorated" does not know this is happening.

This is therefore a key technique to allow the app to stay decoupled, so that it doesn’t degrade into the proverbial "big ball of mud". There’s a lot more discussion on this topic in modules, below.

Mixins are also a convenient mechanism for grouping functionality even for a concrete type, helping to rationalize about the dependency between the data and the behaviour. Each mixin is in effect a single behavioural "responsibility" of the domain object.

In fact, we find mixins nicely balance inside-out vs outside-in ways of thinking about a system:

  • inside-out tends to focus on the structure, the nouns that make up the domain.

  • outside-in tends to focus on the behaviour, that is the functionality that the system provides to automate the business processes; the verbs, in other words.

So, while Apache Isis allows you to put behaviour onto the underlying domain entities, it can often be better to treat the domain entities as immutable. Instead, use mixins to implement behaviour. When using an agile development methodology, it’s common for a user story to correspond to a new mixin.

There are also practical reasons for moving behaviour out of entities even within the same module, because structuring your application this way helps support hot-reloading of Java classes (so that you can modify and recompile your application without having to restart it). This can provide substantial productivity gains.

The Hotspot JVM has limited support for hot reloading; generally you can change method implementations but you cannot introduce new methods. However, the DCEVM open source project will patch the JVM to support much more complete hot reloading support. There are also commercial products such as JRebel.

Details on how to actually write a mixin (the programming model for mixins) is here.

DCI Architecture

Mixins are an implementation of the DCI architecture architecture, as formulated and described by Trygve Reenskaug and Jim Coplien. Reenskaug was the inventor of the MVC pattern (and also the external examiner for Richard Pawson’s PhD thesis), while Coplien has a long history in object-orientation, C++ and patterns.

DCI stands for Data-Context-Interaction and is presented as an evolution of object-oriented programming, but one where behaviour is bound to objects dynamically rather than statically in some context or other. The mixin pattern is Apache Isis' straightforward take on the same basic concept.

Identifiers

The Apache Isis framework tracks the identity of each domain object. This identity is represented to the end-user in human-readable form so that they know which object they are interacting with, and is also used and is available internally/for integrations.

This section explores these two related concepts.

Title, Icon etc.

To allow the end-user to distinguish one domain object from another, it is rendered with a title and an icon. The icon informally identifies the type of the domain object, while the title identifies the instance.

  • Title

    The title of a domain object is shown in several places: as the main heading for an object; as a link text or tooltip for an object referencing another object, and also in tables representing collections of objects.

    The title is not formally required to be a unique identify the object within its type, but it needs to be "unique enough" that a human user is able to distinguish one instance from another.

    The title is usually just a simple string, but the framework also allows for the title to be translated into different locales.

  • Icon

    Sometimes it’s helpful for the icon to represent more than just the object’s type; it might also indicate the state of an object. For example, a shipped Order might have a slightly different icon to a yet-to-be-shipped Order; or a library book that is loaned out might be distinguished from one that is available.

Details on how to actually write titles and icons (the programming model) can be found here.

OIDs

As well as defining a metamodel of the structure (domain classes) of its domain objects, Apache Isis also manages the runtime instances of said domain objects.

When a domain entity is recreated from the database, the framework keeps track of its identity through an "OID": an object identifier. Fundamentally this is a combination of its type (domain class), along with an identifier. You can think of it as its "primary key", except across all domain entity types.

For portability and resilience, though, the object type is generally an alias for the actual domain class: thus "customers.CUS", say, rather than "com.mycompany.myapp.customers.Customer". This is derived from an annotation. The identifier meanwhile is always converted to a string.

Although simple, the OID is an enormously powerful concept: it represents a URI to any domain object managed by a given Apache Isis application. With it, we have the ability to lookup any arbitrary domain objects.

Some examples:

  • an OID allows sharing of information between users, eg as a deep link to be pasted into an email.

  • the information within an OID could be converted into a barcode, and stamped onto a PDF form. When the PDF is scanned by the mail room, the barcode could be read to attach the correspondence to the relevant domain object.

  • as a handle to any object in an audit record, as used by AuditerService;

  • similarly within implementations of CommandService to persist Command objects

  • similarly within implementations of PublisherService to persist published action invocations

  • and of course both the RestfulObjects viewer and Wicket viewer use the oid tuple to look up, render and allow the user to interact with domain objects.

Although the exact content of an OID should be considered opaque by domain objects, it is possible for domain objects to obtain OIDs. These are represented as Bookmarks, obtained from the BookmarkService. Deep links meanwhile can be obtained from the DeepLinkService.

OIDs can also be converted into XML format, useful for integration scenarios. The common schema XSD defines the oidDto complex type for precisely this purpose.

Object Members

Every domain object in Apache Isis consists of (at most) three types of members:

  • properties, such as a Customer's firstName

  • collections, such as a Customer's orders collection of Orders

  • actions, such as a Customer's placeOrder(…​) method.

In addition, mixins act as contributors of behaviour to an underlying domain object. Typically the behaviour being contributed is an action, but it could also be a derived property or a derived collection.

What follows is a simplification; the Apache Isis programming model also recognizes a number of other supporting methods for domain object members, for associated business logic. This is covered in more detail in business rules.

Properties

Properties follow the standard getter/setter pattern, with the return type being a scalar (a value object or another entity or view model).

For example (using Project Lombok to avoid some boilerplate), with:

public class Customer
    @Property                       (1)
    @PropertyLayout                 (2)
    @Getter @Setter
    private String lastName;
    ...
}
1 The @Property annotation defines additional domain-layer semantics
2 The @PropertyLayout annotation defines additional presentation-layer hints

From this the framework infers the Customer domain entity, which in turn has a firstName string property.

@Property is not mandatory, though in many cases it will be present in order to specify additional semantics.

@PropertyLayout is not mandatory either. Whether it is present or not depends to some extent on your preferred style: the UI semantics can be specified either in code within this annotation, or can be specified through the companion layout file.

Details on how to actually write properties in practice (the programming model) can be found here.

Collections

Like properties, collections are also represented by a getter and setter, however the return type is a Collection or subtype.

For example (again, using Project Lombok), with:

public class Customer
    @Collection                                             (1)
    @CollectionLayout                                       (2)
    @Getter @Setter
    private SortedSet<Order> orders = new TreeSet<Order>(); (3)
    ...
}
1 The @Collection annotation defines additional domain-layer semantics
2 The @CollectionLayout annotation defines additional presentation-layer hints
3 The most commonly a java.util.SortedSet for entities obtained from an RDBMS (with set semantics).
all entities should define a natural ordering so that when rendered in the UI they will be ordered "meaningfully" to the end-user.

From this the framework infers the orders collection.

As with properties, the @Collection annotation is not mandatory, though in many cases it will be present in order to specify additional semantics.

Similarly, @CollectionLayout is not mandatory either. Whether it is present or not depends to some extent on your preferred style: the UI semantics can be specified either in code within this annotation, or can be specified through the companion layout file.

Details on how to actually write collections in practice (the programming model) can be found here.

Actions

While properties and collections define the state held by a domain object (its "know what" responsibilities), actions define the object’s behaviour (its "know how-to" responsibilities).

An application that consists only of domain entities with just "know-what" responsibilities is pretty dumb: it requires that the end-user know the business rules and doesn’t modify the state of the domain objects such that they are invalid (for example, an "end date" being before a "start date"). Such applications are often called CRUD applications (create/read/update/delete).

In more complex domains, it’s not realistic/feasible to expect the end-user to have to remember all the different business rules that govern the valid states for each domain object. Actions allow those business rules to be encoded programmatically. Those actions can either be defined in the domain class itself, or can be contributed by way of a mixin.

The general philosophy for an Apache Isis (naked objects) application is not to constrain the end-user in how they interact with the UI: it doesn’t attempt to define a rigid business process. However, it does aim to ensure that business rule invariants are maintained, that is that domain objects aren’t allowed to enter into an invalid state.

For simple domain applications, you may want to start prototyping only with properties, and only later introduce actions (representing the most common business operations). But an alternative approach, recommended for more complex applications, is actually to start the application with all properties non-editable. Then, as the end-user requires the ability to modify some state, there is a context in which to ask the question "why does this state need to change?" and "are their any side-effects?" (ie, other state that changes at the same time, or other behaviour that should occur). If the state change is simple, for example just being able to correct an invalid address, or adding a note or comment, then that can probably be modelled as a simple editable property. But if the state change is more complex, then most likely an action should be used instead.

Broadly speaking, actions are those public methods that do not represent properties or collections.

For example:

public class Customer {
    @Action                       (1)
    @ActionLayout                 (2)
    public Customer placeOrder(
        @Parameter                (3)
        @ParameterLayout          (4)
        Product p,
        @Parameter
        @ParameterLayout
        int quantity) {
        /* ... */
    }
    ...
}
1 The @Action annotation defines additional domain-layer semantics
2 The @ActionLayout annotation defines additional presentation-layer hints
3 The @Parameter annotation defines additional domain-layer semantics
4 The @ParameterLayout annotation defines additional presentation-layer hints

From this the framework infers a the placeOrder action.

Whether the @Action annotation is required to identify an action method is configurable; some teams prefer actions to be explicitly called out, others prefer that any "left over" public methods are identified as actions.

The use of @ActionLayout is optional, the UI semantics can be specified either in code within this annotation, or can be specified through the companion layout file.

As with properties, both @Parameter and @ParameterLayout are also optional. Note though that UI hints for parameters cannot be specified in the layout file.

Details on how to actually write actions in practice (the programming model) can be found here.

Business Rules

When a domain object is rendered in the UI or the end-user interacts with the domain object through the UI, the framework applies a series of precondition business rules to each object member (property, collection or action).

When the object is being rendered, the framework checks:

  • is the object member visible?

    Members that are not visible are simply omitted from the page. If all the members in a fieldset (property group) are hidden, then the fieldset is not shown. If all the members in a tab are hidden, then the tab is not shown. If all the members of the object are hidden, then a "404" style message ("no such object") is returned to the user.

  • if the object member is visible, is the object member enabled?

    An enabled property can be edited (otherwise it is read-only), and an enabled action can be invoked (otherwise it’s button is "greyed-out"). Note that collections are always read-only.

  • for enabled object members, if the user then interacts with that member, are the supplied values valid (can the user "do it").

    For an editable property this means validating the proposed new value of the property. For an invokable action this means validating that arguments being used to invoke the action.

One way to remember this is: "see it, use it, do it"

See it, use it, do it
  • is the object member visible?

  • if so, is the object member enabled?

  • if so, are the supplied values valid? (can the user "do" it)?

The framework provides a multitude of ways to implement these business rules. The simplest approach is to just implement the business rules imperatively in the domain object, using a supporting method. For example,

public Customer placeOrder(Product p, int quantity) {
    // ...
}
public boolean hidePlaceOrder() {                       (1)
    return isBlacklisted();
}
1 supporting method, invoked before rendering the customer.

In this example the "place order" action would not be visible for any customer that had been blacklisted.

Details on how to actually write business rules (the programming model) can be found here.

Events

When the framework renders a domain object, and as the end-user interacts with the domain object, the framework it emits multiple events using the intra-process event bus. These events enable other domain services (possibly in other modules) to influence how the domain object is rendered, or to perform side-effects or even veto an action invocation.

To receive the events, the domain service should subscribe to the EventBusService, and implement an appropriately annotated method to receive the events.

The framework has several categories of events: domain events, UI events and lifecycle events. These are explored in the sections below.

Domain Events

Domain events are fired — through the internal event bus — for every user interaction with each object member (property, collection or action).

By default, rendering a property causes a PropertyDomainEvent to be fired, though the @Property#domainEvent() attribute allows a custom subclass to be specified if necessary. Similarly, rendering a collection causes a CollectionDomainEvent to be fired, and rendering an action causes an ActionDomainEvent to be fired.

In fact, each event can be fired up to five times, with the event’s getEventPhase() method indicating to the subscriber the phase:

  • hide phase allows the subscriber to hide the member

  • disable phase allows the subscriber to disable the member.

    For a property this makes it read-only; for an action this makes it "greyed out". (Collections are implicitly read-only).

  • validate phase allows the subscriber to validate the proposed change.

    For a property this means validating the proposed new value of the property; for an action this means validating the action parameter arguments. For example, a referential integrity restrict could be implemented here.

  • executing phase is prior to the actual property edit/action invocation, allowing the subscriber to perform side-effects.

    For example, a cascade delete could be implemented here.

  • executed phase is after the actual property edit/action invocation.

    For example, a business audit event could be implemented here.

For more details on the actual domain event classes, see the domain event section of the relevant reference guide.

UI Events

As explained earlier, to allow the end-user to distinguish one domain object from another, it is rendered with a title and an icon.

Normally the code to return title and icon of an object is part of the domain object’s implementation. However, UI events allow this title and icon to be provided instead by a subscriber. UI events have higher precedence than the other mechanisms of supplying a title.

If annotated with @DomainObjectLayout#titleUiEvent(), the appropriate (subclass of) TitleUiEvent will be emitted. Similarly for #iconUiEvent(). In addition, it is possible to use events to obtain a CSS class to render with the domain object, using #cssClassUiEvent(), and to select an alternate layout file using #layoutUiEvent().

There are two use cases where this feature is useful:

  • the first is to override the title/icon/CSS class/layout of 3rd party library code, for example as provided by the SecMan extension.

  • the second is for JAXB-style view models which are code generated from XSDs and so cannot have any dependencies on the rest of the Apache Isis framework.

In this second case a subscriber on the default events can provide a title and icon for such an object, with the behaviour provided using mixins.

Lifecycle Events

Lifecycle events allow domain object subscribers to listen for changes to the persistence state of domain entities, and act accordingly.

Lifecycle events are not fired for view models.

The lifecycle events supported are:

  • created

    Entity has just been instantiated. Note that this requires that the object is instantiated using the framework, see here for further discussion.

  • loaded

    Entity has just retrieved/rehydrated from the database

  • persisting

    Entity is about to be inserted/saved (ie for the first time) into the database

  • persisted

    Entity has just been inserted/saved (ie for the first time) into the database

  • updating

    The (already persistent) entity about to be flushed in the database

  • updated

    The (already persistent) entity has just been flushed to the database

  • removing

    The (already persistent) entity is about to be deleted from the database

For example, if annotated with @DomainObjectLayout#updatingLifecycleEvent, the appropriate (subclass of) ObjectUpdatingEvent will be emitted.

There is no lifecycle event for "entity creating" because (obviously) the framework doesn’t know about newly created objects until they have been created. Similarly, there is no lifecycle event for entities that have been removed because it is not valid to "touch" a domain entity once deleted.

Modules

Enabling and ensuring modularity is a key principle for the Apache Isis framework. Modularity is the only way to ensure that a complex application domain does not over time degenerate into the infamous "big ball of mud", software that is difficult, dangerous and expensive to change.

Modules chunk up the overall application into smaller pieces, usually a pacakge and subpackages. The smaller pieces can be either tiers (presentation / domain / persistence) or functional architectural layers (eg customer vs orders vs products vs invoice etc). Because Apache Isis takes care of the presentation and persistence tiers, modules for us focuses just on the important bit: considering how the functionality within the domain model should be broken up into modules, and determining the dependencies between those modules.

Rules of Thumb

The two main rule of thumbs for dependencies are:

  1. there should be no cyclic dependencies (the module dependencies should form an acyclic graph), and

  2. unstable modules should depend upon stable modules, rather than the other way around.

By "unstable" we don’t mean buggy, rather this relates to its likelihood to change its structure or behaviour over time: in other words its stability as a core set of concepts upon which other stuff can depend. Reference data (calendars, tax rates, lookups etc) are generally stable, as are "golden" concept such as counterparties / legal entities or financial accounts. Transactional concepts such as invoices or agreements is perhaps more likely to change. But this stuff is domain specific.

Decoupling

Having broken up a domain into multiple modules, there is still a need for higher level modules to use lower level modules, and the application must still appear as a coherent whole to the end-user.

The key features that Apache Isis provides to support this are:

  • dependency injection of services

    Both framework-defined domain services and application-defined services (eg repositories and factories) are injected everywhere, using the @javax.inject.Inject annotation (Spring’s @Autowired can also be used).

    By "everywhere", we mean not just into domain services, but also injected into domain entities and view models. This enables us to implement behaviourally complete domain objects (if we so wish).

  • mixins that allow functionality defined in one module to appear (in the UI) to be provided by some other module.

    For example, a Document module might allow Document objects to be attached to any arbitrary domain object (such as Order or Customer) in other modules. A mixin would allow the UI for a Customer to also display these attached Documents, even though the Customer module would have no knowledge of/dependency on the Workflow module. (More on this example below).

    Dependencies are also injected into mixins. A common technique is to factor out from domain objects into mixins and then generalise.

  • the internal event bus allows modules to influence other modules.

    A subscriber in one module can subscribe to events emitted by domain objects in another module. These events can affect both the UI (eg hiding or disabling object members, or allowing or vetoing interactions).

A good example of this last is supporting (what in an RDBMS we would call) referential integrity. Suppose the customers module has a Customer object and a EmailAddress object, with a customer having a collection of email addresses. A communications module might then use those email addresses to create EmailCommunications.

If the customers module wants to delete an EmailAddress then the communications module will probably want to veto this because they are "in use" by those EmailCommunications. Or, it might conceivably perform a cascade delete of all associated communications. Either way, the communications module receives an internal event representing the intention to delete the EmailAddress. It can then act accordingly, either vetoing the interaction or performing the cascade delete. The customers module for its part does not know anything about this other module.

Inverting Dependencies

If we get the dependencies wrong (that is, our initial guess on stability is proven incorrect over time) then there are a couple of techniques we can use:

  • use the dependency inversion principle to introduce an abstraction representing the dependency.

  • move functionality, eg by factoring it out into mixins into the other module or into a third module which depends on the other modules that had a bidirectional relationship

Mixins in particular allow dependencies to be inverted, so that the dependencies between modules can be kept acyclic and under control.

For example, suppose that we send out Invoices to Customers. We want the invoices to know about customers, but not vice versa. We could surface the set of invoices for a customer using a Customer_invoices mixin:

diagram
Figure 2. invoices module contributes to customers

In the UI, when rendering a Customer, we would also be presented with the associated set of Invoices.

We can also use mixins for dependencies that are in the other direction. For example, suppose we have a mechanism to attach Documents to arbitrary domain objects. The documents module does not depend on any other modules, but provides a DocumentHolder marker interface. We can therefore attach documents to a Customer by having Customer implement this marker interface:

diagram
Figure 3. customers depends upon contributions of documents

Defining Modules

In the context of Java applications, modularity is a rather overloaded term. We have Maven modules, Java 9 modules and we also have Spring @Configurations, which define a set of domain services.

In the context of Apache Isis, a module is actually the last of these, a Spring module.

The simpleapp starter app provide some structure and illustrates the idioms. To summarise:

  • by convention, we have one @Configuration module per Maven module.

    This is at the root package of the maven module.

  • all of the domain classes (domain objects and services) are part of that Maven module.

    All are annotated or meta-annotated with Spring’s @Component annotation, and Spring’s @ComponentScan is used to discover these from the classpath.

  • Spring’s @Import is used to express a dependency between each "configuration" module.

By convention, we have just one Spring module to each Maven module. This means that the dependencies between Maven modules (using <dependency> are mirrored in the Spring module’s @Import statements). We can therefore rely on Maven to ensure there are no cyclic dependencies: the application simply won’t compile if we introduce a cycle.

Details on how to actually define modules can be found here.

If the above convention is too officious,then you could choose to have multiple Spring modules per Maven module, but you will need to watch out for cycles.

In such cases (proprietary) tools such as Structure 101 can be used to help detect and visualize such cycles. Or, (open source) libraries such as ArchUnit or jQAssistant can help enforce architectural layering to prevent the issue arising in the first place. (These tools can enforce other conventions, too, so are well worth exploring).

Programming Model

Apache Isis works by building a metamodel of the domain objects: entities, domain services, view models and mixins. Dependent on the sort of domain object, the class methods represent both state — (single-valued) properties and (multi-valued) collections — and behaviour — actions.

More specifically, both entities and view models can have properties, collections and actions, while domain services have just actions. Mixins also define only actions, though depending on their semantics they may be rendered as derived properties or collections on the domain object to which they contribute.

In the automatically generated UI a property is rendered as a field. This can be either of a value type (a string, number, date, boolean etc) or can be a reference to another entity. A collection is generally rendered as a table.

Additional business rules semantics are inferred both imperatively from supporting methods (such as disableXxx()) and declaratively from annotations.

Taken together this set of conventions are what we call the Apache Isis Programming Model. In essence, these conventions are just an extension of the pojo / JavaBean standard of yesteryear: properties and collections are getters/setters, while actions are simply any remaining public methods.

In fact, the Apache Isis programming model is extensible; you can teach Apache Isis new programming conventions and you can remove existing ones; ultimately they amount to syntax. The only real fundamental that can’t be changed is the notion that objects consist of properties, collections and actions.

You can learn more about extending Apache Isis programming model here.