Naked Objects: 14 Years On

Dan Haywood & Jeroen van der Wal

A Quick Introduction

  • Apache Isis is a Java framework that uses Apache Wicket and JBoss RestEasy (JAX-RS) for its presentation layer, and DataNucleus for its persistence layer (using JDO).

  • To use Apache Isis you don’t need to know any Wicket or JAX-RS, but you will over time become familiar with DataNucleus' JDO annotations.

  • Apache Isis is itself built using Apache Maven, and the framework provides a Maven archetype as the starting point for your own domain applications

A framework, not a library

  • Apache Isis is a framework, not a library

  • (Generally speaking) the framework calls your code, not the other way around

    • acts as a container

  • It implements the hexagonal architecture (also known as ports-and-adapters).

For Problem Solvers…​

  • Apache Isis' sweet spot is for back-office "line-of-business" systems, for complex domains

  • The end-users of such systems are often quite expert in the domain

  • The naked objects philosophy is to empower such users with the tools to do their job, without prescribing how to do that job

  • The Wicket viewer is designed for this environment

…​ or Process Followers

  • On the other hand, sometimes the users are not domain experts and require more guidance

  • The Wicket viewer does provide an extensibility API to allow more customized/tailored views

  • Alternatively, the REST API automatically provided by the Restful Objects viewer can be used as the backend to custom mobile apps (eg AngularJS) or for microservices

  • A Swagger UI makes the REST API easy to develop against

Applib

  • The Apache Isis "applib" is the single point of contact between domain objects and the rest of the framework

  • It mainly consists of the API to a variety of domain services

  • It also defines a number of value types that can optionally be used (eg Password, Blob) and for i18n support (TranslatableString)

Exercise: Choose a domain

  • Choose a small domain to work on throughout this tutorial

    • 3 or 4 entities, say

  • For example:

    • holiday cottage letting

    • lending library

    • restaurant reviews

    • contact manager

    • taxi-ride booking system

About this tutorial

  • This tutorial lives at www.danhaywood.com/spa2016

  • Use the arrow keys to move from topic to topic

    • to the detail on current topic

    • and for prev/next topic,

    • see the legend bottom right to see which direction(s) you can traverse

  • Press esc to go to an overview of the entire slide deck

Exercise: Navigate around

  • At the end of each section is an exercise slide, like this one

    • exercise slides have a slightly different background colour

  • Use the exercises as a starting point for your own explorations

  • Feel free to break off at any time and explore

  • For now, use the arrow keys and esc to quickly get an idea of what else is in this tutorial

Prerequisites

  • Apache Isis is a Java framework, so you’ll need:

    • Java JDK

    • Apache Maven

    • an IDE

JDK and Maven

  • You’ll need JDK 8 (preferred) or 7

  • You’ll also need Apache Maven to be installed

IDEs

Documentation

This tutorial has plenty of links to useful documentation, but you might also want to:

Exercise: Set up your environment

  • Use the above notes to set up Java, Maven and an IDE

  • Optionally, install the file templates/live templates

  • If using Eclipse, install the DataNucleus plugin

Exercise: Simpleapp archetype

  • Alternatively, you can use the most recent released version of the framework using:

mvn archetype:generate  \
    -D archetypeGroupId=org.apache.isis.archetype \
    -D archetypeArtifactId=simpleapp-archetype \
    -D archetypeVersion=1.12.2 \
    -D groupId=com.mycompany \
    -D artifactId=myapp \
    -D version=1.0-SNAPSHOT \
    -B

Exercise: Build the app

  • The newly generated app can be built from the command line:

    cd myapp
    mvn clean install
  • Better still though is to import the app into the IDE

Exercise: Run the app

Explore the Generated App

  • the dom mvn module holds the domain object model

    • SimpleObject is a domain entity

    • SimpleObjects is corresponding repository/menu

  • The fixtures module defines fixtures for use in prototyping and tests

    • by default, run in-memory HSQLDB

    • RecreateSimpleObjects creates a complete set of entities

Exercise: Explore the Generated App

  • the integtests module holds end-to-end integration tests running against in-memory DB and using the fixture script

  • the app module defines a subclass of the AppManifest class, defining the "scope" of the application

  • the webapp module contains WEB-INF/web.xml etc

    • WEB-INF/isis.properties and related files configure the framework

Exercise: Rename SimpleObject

  • Rename SimpleObject to a core entity of your domain

    • eg to Customer

    • rename class name in JDOQL annotations also

  • rename corresponding SimpleObjects repository also

    • eg Customers

  • rename the RecreateSimpleObjects fixture

    • eg RecreateCustomers

  • add further entities for your domain model

Domain Entities

  • Domain entities are the building blocks of Apache Isis applications, being the persistent objects with which end-users view and interact

  • From a programming perspective, they are basically pojos, with some JDO annotations

Object Title

  • The title allows the end-user to distinguish object instances within the UI

    • Used as text for hyperlink to navigate to that object

  • Typically implemented using title() method:

    public String title() {
      return getName();
    }
  • Alternatively can use @Title annotation

Object Icon

  • The icon helps distinguish different types of object, and sometimes their state

  • Typically a static .png, eg Customer.png

  • Can optionally specify a dynamic icon using iconName() method, used as suffix:

    public String iconName() {
      return isPremium()?"-premium":"";
    }

    would use Customer-premium.png for premium customers.

Object domain-layer Semantics

  • The Apache Isis @DomainObject annotation is used to specify domain-layer semantics

    • whether read-only or editable, using #editing

    • whether audited using #auditing

    • whether published using #publishing

    • hook into JDO lifecycle events

Object UI-layer semantics

  • The .layout.xml file describes how to layout class members within a (bootstrap3) layout grid

    • eg Customer.layout.xml

  • Grid made up of rows with nested cols

    • 12 cols per row

  • Within a col, have tabGroups (+ child tabs) or fieldsets

  • Reference domain object (ie the title), and the various class members

UI-layer semantics using annotations (ctd)

  • one col can hold <domainObject> element to specify location of title/icon

  • can specify same info as @DomainObjectLayout

    • override name inferred from code

    • CSS class, icon name, description

    • whether to bookmark, page size (if returned as a list)

  • use annotation to hook into UI lifecycle events for title, icon, CSS class.

Exercise: titles and icons

  • Implement a title to identify your domain entity

    • you may need to add additional properties (covered in next section in more detail)

  • Select an icon, eg using an icon from icons8.com or similar

  • Experiment with .layout.xml file

    • edit it within an IDE (code completion should work)

    • rebuild (in IntelliJ: "reload changed classes")

    • what happens if the file is malformed?

(Value) Properties

  • Properties are defined as a normal getter/setter pair

    private String name;
    public String getName() {
      return this.name;
    }
    public void setName(String name) {
      this.name = name;
    }
  • Used by both Apache Isis (for the UI) and by DataNucleus (for the DB)

Property types

  • The property’s type can be a primitive, a wrapper type, a Joda time type, or an Isis-specific type (eg Blob/Clob)

Property optionality

  • In JDO, primitives are always mandatory but every other type is assumed optional

  • Apache Isis however all properties are assumed mandatory unless otherwise specified

  • Use the JDO @Column(allowsNull=…​) annotation to specify optionality

    • recognized by Apache Isis also

Other JDO annotations documented here

Property domain-layer semantics

  • The Apache Isis @Property annotation is used to specify domain-layer semantics

    • whether editable

    • whether to raise a domain event on internal event bus

    • whether to publish via the PublisherService

    • whether to reify as a command

      • defer execution to run in background

Property domain-layer semantics (ctd)

  • Other semantics specified using java/javax annotations

    • For strings, specify max. length using @Column

    • For decimals, specify scale using @Digits

Project Lombok

  • You can also use Project Lombok to remove the getter/setters

  • Usually as simple as adding the mvn dependency:

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.6</version>
    </dependency>
  • allowing us to write:

    @Getter @Setter
    private String name;

Property UI-layer semantics

  • The .layout.xml fieldsets can reference property via <property> element

  • Can specify same info as @PropertyLayout annotation

    • override name inferred from code

    • CSS class, description

    • positioning of label

    • if multiline (text area, not text field)

    • whether to render dates as day before (for 'end' date ranges)

Property UI-layer semantics (ctd)

  • can optionally use @MemberOrder annotation

    • associate a property to a fieldset (using name)

    • save listing all properties in the .layout.xml

  • Not possible to specify all layout options with annotations (eg no tabs)

  • Not dynamically reloadable (whereas .layout.xml is)

Ignoring Properties

Exercise: flesh out your entity/ies

  • Add further value properties to your domain entity/ies

    • update .layout.xml as you do so

  • Consider which properties are mandatory, which are optional

  • Update domain services as required

Domain Services

  • The framework automatically manages domain services

    • both application-defined and framework-provided

  • API services are called by domain application

  • SPI services are called by framework itself

Domain Service Use Cases

  • Repository for an entity, eg CustomerRepository

  • Menu actions, eg CustomerMenu

  • Facade over some technology, eg BarcodeScanner

Framework-provided services

Application-written services

@DomainService
public class CustomerRepository {
  ...
}

Repository Implementations

Injected

  • Domain services are automatically injected into entities and other domain services

  • eg:

    @DomainService
    public class Customer {
      ...
      @Inject
      OrderRepository orderRepository;
    }

AppManifest

  • The AppManifest specifies which packages to search for both domain services and domain entities

public class DomainAppAppManifest
                             implements AppManifest {
  public List<Class<?>> getModules() {
    return Arrays.asList(
      DomainAppDomainModule.class,  // dom module
      DomainAppFixtureModule.class, // fixture module
      DomainAppAppModule.class      // app module
    );
  }
  ...
}

Exercise: repositories and menus

  • Split out domain services for repository vs menu, and inject the former into the latter

  • Use @DomainServiceLayout#named as a mechanism to group several related menus together, with a menu separator shown between each

Fixture Scripts

  • By default, Apache Isis applications are configured to run against an in-memory database

  • Fixture scripts are a mini-framework to help set up data

  • Top-level fixture script represent a scenario

    • to start the conversation while prototyping

    • or the "given" of an integration test

Typical script to create an object

public class SimpleObjectCreate
                         extends FixtureScript {
  @Setter
  private String name;
  @Getter
  private SimpleObject simpleObject;
  protected void execute(ExecutionContext ec) {
    String name = checkParam("name",ec,String.class);
    this.simpleObject = simpleObjects.create(name)
    ec.addResult(this, this.simpleObject);
  }
  @Inject
  private SimpleObjects simpleObjects;
}

Typical script to teardown objects

public class SimpleObjectsTearDown
                        extends FixtureScript {
  protected void execute(ExecutionContext ec) {
    isisJdoSupport.executeUpdate(
      "delete from \"simple\".\"SimpleObject\"");
  }
  @Inject
  private IsisJdoSupport isisJdoSupport;
}

Typical script to set up a scenario

public class RecreateSimpleObjects
                        extends FixtureScript {
  ...
  public final List<String> NAMES = ...
  @Getter
  private List<SimpleObject> simpleObjects = ...;
  protected void execute(ExecutionContext ec) {
    ...
    ec.executeChild(this,new SimpleObjectsTearDown());
    for (String name: NAMES) {
      SimpleObjectCreate fs =
          new SimpleObjectCreate().setName(name);
      ec.executeChild(this, name, fs);
      simpleObjects.add(fs.getSimpleObject());
    }
  }
}

AppManifest to automatically run fixtures on start-up

public class DomainAppAppManifestWithFixtures
                      extends DomainAppAppManifest {
  public List getFixtures() {
    return Lists.newArrayList(
                      RecreateSimpleObjects.class);
  }
  public Map getConfigurationProperties() {
    return ImmutableMap.of(
      "isis.persistor.datanucleus.install-fixtures",
      "true");
  }
}

Exercise: add fixture scripts

  • Develop one or more fixture scripts to represent typical starting scenarios within your application

  • Create a custom AppManifest to automatically install this fixture each time the app is run

Associations

  • Associations relate entities together

    • 1:1 or 1:m

    • unidirectional or bidirectional

  • Implemented either as a foreign key or as a join table

IntelliJ Live Templates

  • For properties:

    isp jdo
  • For collections:

    isc jdo

Unidirectional 1:1 Reference Property

  • eg reference to a lookup/static data

  • corresponds to a simple foreign key

    public class Product {
        ...
      @Column(allowsNull = "false")
      @Property()
      @Getter @Setter
      private TaxRate taxRate;
    }

Bidirectional 1:m Collection

  • eg order/order-detail, an aggregate root with child elements

  • corresponds to a foreign key, traversed in both directions

    public class Order {
        ...
      @Persistent(
        mappedBy = "order",
        dependentElement = "true"
      )
      @Collection()
      @Getter @Setter
      private SortedSet<OrderItem> orderItems =
                            new TreeSet<OrderItem>();
    }

Bidirectional 1:m Join-table

  • to avoid the FK in the "many" side of the association

public class Customer {
  ...
  @Persistent(
    mappedBy = "customer",
    dependentElement = "false"
  )
  @Join
  @Collection()
  @Getter @Setter
  private SortedSet<Order> orders =
                        new TreeSet<Order>();
}

Collection domain-layer semantics

  • The Apache Isis @Collection annotation is used to specify domain-layer semantics

    • whether to raise a domain event on internal event bus

Collection UI-layer semantics

  • The .layout.xml cols can reference collection via the <collection> element

  • Can specify same info as @CollectionLayout annot’n

    • override name inferred from code

    • CSS class, description

    • default view (table, hidden, …​)

    • page size

    • how to sort (if not default)

Exercise: entity associations

  • Associate the entities of your domain model

    • update .layout.xml as you do so for the new reference properties and collections

  • Update your fixture scripts to set up example objects

Actions

  • Any public method is an action

    public class Customer {
      public Order placeOrder(
          Product p,
          int quantity) {
        ...
      }
    }
  • Contains arbitrary business logic

  • Object returned is rendered next

Action parameter names

  • Java bytecode (prior to JDK 8) does not capture parameter name

  • Use @ParameterLayout to specify parameter name for primitives

public class Customer {
  public Order placeOrder(
      Product p,
      @ParameterLayout(named="Quantity")
      int quantity) {
    ...
  }
}

paraname8 module

  • Alternatively, configure the paraname8 module

  • One of a number of (non-ASF) modules catalogued at Isis  Addons.

Action domain-layer semantics

  • The Apache Isis @Action annotation is used to specify domain-layer semantics

    • whether to raise a domain event on internal event bus

    • semantics (idempotency etc)

    • 'are you sure' indicator

    • if can invoke on collection of objects, not just one

    • whether show in prototyping mode only

Action UI-layer semantics

  • The .layout.xml can reference action via the <action> element

  • if contained within:

    • fieldset, render on panel

    • property, render below that property

    • collection, render on collection’s header

    • if contained within a <col>, render in that location

Action UI-layer semantics (ctd)

  • Can specify same info as @ActionLayout annotation

    • override name inferred from code

    • CSS class, description

    • whether to bookmark (for query-only actions)

    • fine-tune the position as button vs drop-down

    • either using .layout.xml or using @MemberOrder.

  • associate an actions with a property or a collection

Action UI-layer semantics (ctd)

  • can optionally use @MemberOrder annotation

    • associate to a property or collection (using #name)

    • saves listing all properties in the .layout.xml

  • Not possible to specify all layout options with annotations (eg no tabs)

  • Not dynamically reloadable (whereas .layout.xml is)

Action Parameter semantics

  • The Apache Isis @Parameter annotation is used to specify domain-layer semantics about the action’s parameters

  • The Apache Isis @ParameterLayout annotation is used to specify UI-layer semantics about the action’s parameters

Exercise: add actions

  • add new behaviour/responsibilities to your domain model objects using actions

    • you may be able to remove some repository/menus - generally the responsibility to create new objects resides with existing objects

    • update .layout.xml as you do so for the new reference properties and collections

  • rework fixture scripts to use new actions

  • consider removing the ability to edit properties

    • actions generally better capture the user’s intent

    • use @Property#editing annotation

Action (Reference) Parameters

  • Action parameters referencing objects are specified using a drop-down

    • Also when editing reference properties

  • The values shown in this drop-down may be supplied in variety of ways

Enums

  • Enums are automatically rendered as a drop-down

public enum PaymentMethodType {
  VISA,
  MASTERCARD,
  AMEX
  CASH
}

Bounded

  • Use #bounded for objects where there is fixed (bounded) number of instances

    • eg lookup or static/reference data

@DomainObject(bounded=true)
public class Country {
  ...
}

Choices

  • Use choicesXxx() supporting method to provides list of choices for parameter N

public ShoppingCartItem addFavorite(
    @ParameterLayout(named="Favorite")
    Product product,
    @ParameterLayout(named="Quantity")
    final Integer quantity) {
  ...
}
public Collection<Product> choices0UpdateProduct() {
  return productRepo.favoritesForUser();
}

AutoComplete

public ShoppingCartItem addProduct(
    Product product,
    @ParameterLayout(named="Quantity")
    final Integer quantity) {
  ...
}
public List<Product> autoComplete0UpdateProduct(
                         @MinLength(2) String name) {
  return productRepo.findByNameOrSku(name);
}

AutoComplete via repository

@DomainObject(
  autoCompleteRepository=CustomerRepository.class
)
public class Customer {
  ...
}
@DomainService
public class CustomerRepository {
    List<Customer> autoComplete(String search);
  ...
}

Dependent choices

@ActionLayout(
  describedAs = "Update category and subcategory")
@Action(semantics = SemanticsOf.IDEMPOTENT)
public Categorized updateCategory(
     Category category,
     @Nullable
     Subcategory subcategory) {
  ...
}
public List<Subcategory> choices1UpdateCategory(
    Category category) {
  return Subcategory.listFor(category);
}

Exercise: drop-downs and defaults

  • update existing behaviour to take advantage of drop-downs

  • improve the user experience further by also offering default values for parameters

Business Rules

  • Use business rules to enforce pre-conditions on property edits/action invocations

  • hide object member (see it?)

    • user prevented from even seeing the property/action

  • disable object member (use it?)

    • greyed out, typically the object’s state is invalid

  • validate (do it?)

    • can edit the property/invoke the action, but validate the new value/action parameters

Hide (See it?)

Disable (Use it?)

  • Specify imperatively

  • Specify declaratively

Validate (Do it?)

Specification

public class StartWithCapital
               implements Specification {
  public String satisfies(Object proposedObj) {
    String proposed = (String)proposedObj;
    return proposed.length() == 0 ||
       !Character.isUpperCase(proposed.charAt(0))
         ? "Does not start with a capital letter"
         : null;
  }
}

Specification (ctd)

public class Customer {
  @Property(mustSatisfy=StartWithCapital.class)
  public String getFirstName() {
    ...
  }
}
public class Product {
  public void updateName(
      @Parameter(mustSatisfy=StartWithCapital.class)
      String name) {
  ...
  }
}

Exercise: business rules

  • Identify pre-conditions for existing actions

  • Implement as either a hide, disable or validate

Unit Testing

  • Since the framework promotes dependencies injection, unit testing is straightforward

  • The framework also provides some helper classes

Example

  • Given:

public class Customer {
  @Action
  public Order newOrder() { ... }
  public String disableNewOrder() {
    return isBlacklisted()
      ? "sorry, customer is blacklisted"
      : null;
  }
}

Example (ctd)

  • Test using:

public class CustomerTest {
  @Test
  public void blacklisted_cannot_create_order() {
    Customer cust = new Customer();
    cust.setBlacklisted(true);
    assertNotNull(cust.disableNewOrder());
  }
}

JMock Extension

  • You can use any mocking framework, but the framework provides some additional helper classes for JMock

  • eg, given:

public class Collaborating {
  ...
  Collaborator collaborator;
  public void setCollaborator(Collaborator collaborator) {
    this.collaborator = collaborator;
  }
}

JMock Extension (ctd)

public class CollaboratorTest {
  @Rule
  public JUnitRuleMockery2 context =JUnitRuleMockery2
             .createFor(Mode.INTERFACES_AND_CLASSES);
  @Mock
  Collaborator collaborator;
  @ClassUnderTest
  Collaborating collaborating;
  @Test
  public void collaborator_is_autowired() {
    assertNotNull(collaborating.collaborator);
  }
}

Exercise: Write a unit test

  • Using your preferred unit testing framework

    • verify the behaviour of an action

    • verify a business rule

  • use conventional test-first development to implement a new feature

    • does Apache Isis help/hinder this approach?

Integration Testing

WrapperFactory

  • Given:

public class Customer {
  @Action
  public Order newOrder() { ... }
  public String disableNewOrder() {
    return isBlacklisted()
      ? "sorry, customer is blacklisted"
      : null;
  }
}

WrapperFactory (ctd)

  • Test with:

public class CustomerTest {
  @Test(expected=DisabledException.class)
  public void blacklisted_cannot_create_order() {
    Customer cust = repo.findBlacklisted().get(0);
    wf.wrap(cust).newOrder();
  }
  @Inject
  WrapperFactory wf;
  @Inject
  CustomerRepository repo;
}

Fixture script setup not shown in this example.

Fake data

  • Data where "any value will do" should be chosen randomly

    • eg name of a customer

  • Any "hard-coded" data in the test then stands out as relevant to the behaviour being verified

    • eg date of birth of the customer, perhaps

  • The (non-ASF) Isis addons' fakedata module provides the FakeDataService domain service for this purpose

FakeDataService

public interface FakeDataService {
  Names name() { ... }
  Comms comms() { ... }
  Lorem lorem() { ... }
  Addresses addresses() { ... }
  ...
  Strings strings() { ... }
  ...
  IsisBlobs isisBlobs() { ... }
  IsisClobs isisClobs() { ... }
}

Exercise: write an integration test

  • Write an integration test using the WrapperFactory to:

    • verify the behaviour of an action

    • verify a business rule

  • Update your fixture scripts to use the FakeDataService

    • the only hard-coded data should be that relevant to the scenario being tested

View Models

  • Why view models?

    • Not every entity is necessarily persisted by DataNucleus

    • Not every domain object is even an entity

    • High-volume use cases require specialist views to aggregate/consolidate multiple domain objects

    • Exposing domain entities over REST a bad idea if don’t control the client

View Model Support

  • Apache Isis allows view models to be defined to represent such domain objects

    • analogous to an (RDBMS) view on top of an table

  • Several programming models available, but most flexible/straightforward uses JAXB annotations

  • the state of the view model is serialized into the URL (rather than the DB)

JAXB

  • eg:

@XmlRootElement
@XmlType( propOrder = { "left", "right" } )
public class CompareCustomers {

    @XmlElement(required = true)
    @Getter @Setter
    private Customer left;

    @XmlElement(required = true)
    @Getter @Setter
    private Customer right;
}

Referenced entities

  • entities referenced must be annotated with PersistentEntityAdapter

    • serializes out the entity’s id, rather than the entire state of the entity

  • eg:

    @XmlJavaTypeAdapter(PersistentEntityAdapter.class)
    public class Customer {
      ...
    }

Home page

  • Exactly one domain service action can be annotated with @HomePage

    • Automatically invoked whenver user navigates to home

  • Typically returns a view model that acts as a dashboard

Exercise: define a home page

  • Create a view model to represent the home page of your application

  • Annotate a domain service that returns this home page view model

Decoupling

  • You can use Apache Isis to build both monoliths and microservices

    • but often microservices = over-engineering

  • however, monoliths must be modular

  • the framework provides several tools to ensure modularity

Mixins

  • Mixins separate business logic (actions) from state (properties/collections)

  • mixes-in based on object’s type (including interfaces)

    • deepens the (DDD) ubiquitous language

    • eg Annotated, HasEvents

  • Net effect: move behaviour from one module to another

Mixins

public class Customer implements Annotated {
  ...
}
  • mixed-in using:

@Mixin
public class Annotated_annotations {
  Annotated annotated;
  public Annotated_annotations(Annotated annotated) {
      this.annotated = annotated;
  }
  public List<Annotation> annotations() { ... }
}

Event subscribers

  • Event subscribers are notified of events originating in some other module

    • can veto the interaction

    • can react to the interaction, eg cascade delete

  • Net effect: moves validation rules/triggers from one module to another

Event subscribers (ctd)

public class ToDoItem {
  public static class CompletedEvent
             extends ActionDomainEvent<ToDoItem> { }
  @Action(domainEvent=CompletedEvent.class)
  public ToDoItem completed() { ... }
}
  • subscribed to using:

@DomainService
public class SomeSubscriber
               extends AbstractSubscriber {
  @Subscribe
  public void on(ToDoItem.CompletedEvent ev) { ... }
}

WrapperFactory

  • The WrapperFactory should be used for cross-module interactions

    • ie across a trust-boundary

  • any domain events for the callee will be fired

    • akin to no-interface interaction between EJBs (though need to manually wrap)

Poly module

  • The (non-ASF) Isis addons' poly module defines an implementation pattern to decouple modules at the persistence layer

    • a.k.a the "table of two halves" pattern

  • at the domain object layer, associations defined via (Java) interfaces

  • at the database layer, regular foreign keys throughout

  • examples include alias, commchannel and note

Exercise: refactoring

  • Refactor existing action(s) as mixins

  • Refactor business rules as subscribers

  • Update impacted integration tests in both cases

REST API

  • REST API implements the Restful Objects specification.

    • a generic hypermedia API for any domain object, defining both RESTful resources and representations

    • also implemented by Naked Objects framework (.NET)

  • The REST API is inferred automatically from the domain object model

    • eg objects/CUS:123/actions/placeOrder

  • Can also return alternative REST representations

    • as per the HTTP Accept header

Swagger UI

  • Can be accessed at http://localhost:8080/swagger-ui

    • This is a javascript client that downloads the Swagger spec for the domain

  • The swagger spec organizes resources into groups

  • In fact, three variants of the swagger spec, for different client types

Swagger UI Access levels

  • The public API intended for external 3rd party clients

    • ie require backward compatibility

    • exposes only domain services with a nature of VIEW_REST_ONLY and returning view model DTOs

  • The private API intended for internal clients

    • exposes all domain entities

  • The Swagger spec can also be downloaded from Wicket viewer

    • eg to code-generate client stubs in any language

Direct Access

Alternative Representations

  • Alternative representations can be provided by implementing either ContentMappingService or the lower-level

  • The framework provides one suchalternative to that defined by the Restful Objects spec

Exercise: exploring the REST API

  • Use the Swagger UI to explore the REST API available by your client

  • Add VIEW_REST_ONLY domain services to provide a restricted public API

  • (optional) checkout the contactapp, an Apache Isis app with a custom mobile app implemented using Ionic framework (on top of AngularJS)

More on Domain Services

  • This section considers some of the more advanced uses cases for domain services…​

@RequestScoped vs Singletons

  • Most domain services are singletons (application-scoped), but they can also be request-scoped, eg the framework-provided Scratchpad service:

@DomainService
@RequestScoped
public class Scratchpad {
  public Object get(Object key) { ... }
  public void put(Object key, Object value) { ... }
  public void clear() { ... }
}

Replacing framework services

import o.a.i.applib.services.message.MessageService;
@DomainService
@DomainServiceLayout(menuOrder="1")
public class MyMessageService
                      extends MessageService {
  ...
}
  • The lower #menuOrder results in the replacement implementation being injectd rather than the default.

Injecting lists (advanced use case)

  • It’s also possible to inject list of services:

    @DomainService
    public class Customer {
      ...
      @Inject
      List<Printer> printers;
    }
  • All services implementing the type will be injected

  • Useful if decorating a framework-provided domain service

Add-ons

  • The Isis Addons catalog provides:

    • implementations for multiple cross-cutting concerns

      • security, publishing, auditing etc.

    • support for feature toggles

  • The Incode catalog provides reusable business components

Security

  • The security module is the most commonly used add-on

  • User, role, permissions, functions

    • functions inferred from the Apache Isis metamodel

  • Users can be defined locally or delegated (eg from LDAP)

  • Permissions can be

    • scoped to package-level, class or member

    • are additive or subtractive

      • "can only access …​" vs "can access all but…​"

  • Includes an elegant design for multi-tenancy

Publishing

  • The publishmq module implements the PublisherService SPI domain service

  • It publishes events (XML representation of an action invocation or a property edit) onto an ActiveMQ queue

  • Events are optionally persisted, for replay

  • Can associate "status message" objects with events

    • eg for logging/diagnostics, tracking conversations etc

Commands

Auditing

  • The audit module implements the AuditerService SPI domain service

  • Persists every change to every property of every domain object as an audit entry

  • Audit entries correlate back to any persisted command or publishing events, making for a very complete audit record

    • command or publishing captures the "cause"

    • auditing captures the "effect"

Togglz

  • The togglz module integrates the Togglz feature toggle library with Apache Isis

    • The settings module is used to persist which toggles are enabled/disabled

  • The integration includes:

    • a web console to inspect toggles, and

    • a JUnit rule to allow toggles to be controlled within unit/integration tests

Exercise: Integrate an add-on

  • Integrate one of the add-ons into your application

    • Each of the modules has a README with complete instructions

    • Each module also has a demo app; just check out the corresponding repo from github and run

UI Customisations

  • The HTML generated by the Wicket viewer makes it easy to target individual elements using CSS

    • Typically specified in a css/application.css

  • Or, the ComponentFactory SPI allows any element of any page to be replaced with some other rendering

  • The Isis Addons catalog provides various extensions

    • excel, map, calendar, charts etc.

Target individual members

  • Every object member can be targeted by CSS individually

  • eg: the description column of the notYetComplete collection on the ToDoAppDashboard object:

    .ToDoAppDashboard .notYetComplete th.description {
        width: 30%;
    }

CSS classes as domain hints

  • Define application-specific semantics using @PropertyLayout (or .layout.xml); eg:

    @PropertyLayout(cssClass="x-important")
    public LocalDate getDueBy() {
        return dueBy;
    }
  • then target with:

    .x-important label.scalarName {
      color: red;
    }

Combine with cssClass()

  • Similar to iconName(), cssClass() returns an instance-specific CSS class; eg:

    public String cssClass() {
      return !isComplete()? "done": null;
    }
  • then target with:

    tr.done {
        text-decoration: line-through;
        color: #d3d3d3;
    }

Wicket UI components

  • The excel wicket component allows tables to be downloaded as Excel spreadsheets

  • The fullcalendar2 wicket component allows objects with an identified date to be rendered on a full-page calendar

  • The gmap3 wicket component allows objects with a location to be rendered on a Google map

  • The summernote wicket component adds a WYSIWYG text editor to any string property

Exercise: customise the UI

  • use CSS to:

    • change styling of all string properties

    • embolden selected columns within a table

    • with cssClass(), change styling of specified objects as table rows

  • Integrate one of the wicket cpt. add-ons in your app

    • Each of the wicket components has a README with complete instructions

    • Each component also has a demo app; just check out the corresponding repo from github and run

Apache Isis Maven plugin

  • The Maven plugin supports a variety of use cases:

    • validate the metamodel at build time

    • generate swagger specification

    • generate XSDs from view model DTOs

Validation

  • When an Apache Isis app starts up, the metamodel is validated for semantic consistency

    • eg all supporting methods must match up with a class member

  • The validate goal performs this checking during build time, so that such errors are detected as early as possible

Swagger Spec

  • The SwaggerService allows the swagger spec to be downloaded within the app

  • The swagger goal allows the spec to be generated during build time

  • The build can therefore use this swagger spec in downstream build jobs, eg to build and compile client-side stubs in various programming languages

XSD generation

  • JAXB-annotated view models are useful as DTOs for integrating systems, eg over SOAP or an ESB

  • For these scenarios, having an XSD schema corresponding to the JAXB model enables stronger validation in consumers and possibility to code-gen in other languages

  • The xsd goal can be used to generate these XSDs

Exercise: leveraging mvn plugins

  • Force a metamodel validation exception, eg by misspelling a supporting method

    • confirm that rebuilding the app fails in the dom module

Wrap-up

  • what did you build?

  • what did you like?

  • what limitations did you hit/frustrated you?

  • in your opinion, how does the framework:

    • support/hinder agile software development

    • play with/against TDD

    • play with/against BDD?

    • play with/against DDD?