Domain Design


This document is part of the wercstat low-code framework (https://www.wercstat.com).

The following documents are available:

(1) Wercstat Overview: introduction to the framework

(2) Wercstat Getting Started: installation instructions and hello-world tutorial

(3) Wercstat Value Types: description of Java domain value types

(4) Wercstat Server DSL: description of the server-side Domain Specific Language

(5) Wercstat Client DSL: description of the client-side Domain Specific Language


Overview

Components

Wercstat consists of three distinct parts:

  • com.wercstat.domain : the library containing value-types and entity base classes.

  • com.wercstat.frame : the library containing UI components and client-server communication.

  • com.wercstat.plugin: the Eclipse plugin provides the domain-model editor and code-generator.

intro diagram components

Notes:

  • Business code depends on com.wercstat.domain as it provides support for entities and value-types. To prevent vendor lock-in this library is provided as open-source with a permissive license.

  • com.wercstat.frame servers are stateless. The only server-side state is maintained by Vaadin Pages. This makes the application scalable and a good fit for client-side UIs that communicate via REST.

  • com.wercstat.plugin generated code is split into abstract classes with all the logic, and empty concrete sub-classes. Both are by default stored in the src/generated code folder. By adding the custom keyword to the DSL component, the empty concrete class is moved from the src/generated folder to src/main/java, where the developer can add business logic. This ensures there is a clean separation between manual code and generated code.

Server Stack

Most of the server stack consists of open-source software, including the wercstat.io.domain library for value-type and entity support. User interaction logic depends on the wercstat.io.frame libraries, which are vendor dependent.

intro diagram server stack

Technologies

Wercstat is compatible with all major databases, operating systems and cloud providers.

intro diagram technologies

Entity to Page

A typical core-business application consists of hundreds of persistent entities (i.e. database tables). This example illustrates the code required for a new Terms of Delivery entity, from persistence to a web maintenance page (Create, Update, Delete).

intro entity to page
Figure 1. Terms-of-delivery web-page end result

Server side

First step is to create a TermsOfDelivery.real source file for the labels, entity and view-model declaration. DSL files have a .real extensions, which allows Eclipse to recognize the language and provide keyword coloring and auto-completion.

Add labels

Labels are declared with the label-set keyword, and can be translated using standard Java internationalization (i18n) functionality.

Create labels for the required fields, and enumerate values:
label-set TermsOfDeliveryLabels{
	label codeLabel "Code"
	label descriptionLabel "Description" (1)
	label exWorksLabel "Ex Works"

	label transportModeLabel "Transport Mode"
	label transportModeLabelA "Air" icon ICON_AIR (2)
	label transportModeLabelI "Rail" icon ICON_RAIL
	label transportModeLabelR "Road" icon ICON_ROAD
	label transportModeLabelS "Sea" icon ICON_SEA

	label termsOfDeliveryLocationLabel "TOD Location"
	label termsOfDeliveryLocationLabelPLDY "Place of delivery" (2)
	label termsOfDeliveryLocationLabelPLDN "Place of destination"
	label termsOfDeliveryLocationLabelPOST "Port of shipment"
	label termsOfDeliveryLocationLabelPODN "Port of destination"
}
1field labels
2enumerate labels
Add value types

For programming business logic Wercstat does not use primitive type (string, boolean, decimal, etc.) but domain specific value-types.

Create the value-types, using the previously declared labels:
string TermsOfDeliveryCode codeLabel 3
string Description descriptionLabel 30
boolean ExWorks exWorksLabel

enumerate TransportMode transportModeLabel
{
	A: transportModeLabelA (1)
	I: transportModeLabelI
	R: transportModeLabelR
	S: transportModeLabelS
}

enumerate TermsOfDeliveryLocation termsOfDeliveryLocationLabel
{
	PLDY: termsOfDeliveryLocationLabelPLDY (2)
	PLDN: termsOfDeliveryLocationLabelPLDN
	POST: termsOfDeliveryLocationLabelPOST
	PODN: termsOfDeliveryLocationLabelPODN
}
1"A" is the code in the database, and transportModeLabelA ("Air") the label displayed in the form and grid.
2"PLDY" is the code in the database, and termsOfDeliveryLocationLabelPLDY ("Place of delivery") the label displayed in the form and grid.
Add entity

In this example the entity represents the table in the database, and defines all the attributes that constitute a terms-of-delivery.

Create the entity definition, using the previously declared value-types:
entity master TermsOfDelivery termsOfDeliveryLabel
{
	business-key code (1)
	search-paths description (2)

	user-fields { (3)
		attribute TermsOfDeliveryCode code
		attribute Description description
		attribute TransportMode transportMode optional
		attribute TermsOfDeliveryLocation termsOfDeliveryLocation
		attribute ExWorks exWorks default false
	}
}
1terms-of-delivery codes are unique
2when the user enters a search-term to select a record, both the code and description are searched
3all fields are maintained by the user

The database table corresponding with the entity can be created automatically, using the standard Java persistence API (JPA).

intro entity to page table
Figure 2. MySQL database table, created by JPA

System fields are automatically added to register creation date-time, modify date-time, version and data-area (for multi-tenancy).

Add view-model

The view-model defines all the fields and actions that are available in the user-interface. For terms-of-delivery it consists only of entity-fields, no extra form-fields or actions are required.

Create the view-model, using the previously declared entity:
package com.wercstat.erp.client.com.log

viewmodel TermsOfDeliveryViewModel{
	entity TermsOfDelivery
}

Client side project

For a clean server / client separation, create a new TermsOfDeliveryPage.real DSL file for the form, grid and page.

Add grid

The grid displays a list of terms-of-delivery records on the web-page. It has search and sorting functionality by default.

intro entity to page grid
Create the grid, using the previously declared view-model:
grid TermsOfDeliveryGrid
{
	viewmodel TermsOfDeliveryViewModel

	field code
	field description
	field transportMode
	field termsOfDeliveryLocation
	field exWorks
}
Add form

The form provides fields for modifying terms-of-delivery attributes.

intro entity to page form
Create the form, using the previously declared view-model:
form TermsOfDeliveryForm
{
	viewmodel TermsOfDeliveryViewModel

	field code
	field description

	next-column

	field transportMode
	field termsOfDeliveryLocation
	field exWorks
}
Add page

The page combines grids and forms, and links them together with one or more view-models.

Create the page, using the previously declared view-model, grid and form:
page TermsOfDeliveryPage termsOfDeliveryLabel{

	viewmodel TermsOfDeliveryViewModel todViewModel (1)

	view TermsOfDeliveryGrid termsOfDeliveryGrid todViewModel (2)
	view TermsOfDeliveryForm termsOfDeliveryForm todViewModel defaultToolbar (3)

	vertical { (4)
		add termsOfDeliveryGrid expand
		add termsOfDeliveryForm
	}
}
1declare view-model
2link view-model to the grid
3link view-model to the form and add a default toolbar with Remove, New and Save buttons.
4declare the layout of grid and form
Add menu

The menu DSL keyword provides a convenient way to group pages into hierarchical menu’s that can be displayed anywhere in the user interface.

Find the menu for the module (in this case com.log), and add the page to the menu.

Create the menu-entre, using the previously declared page:
menu Menu_com_log com_log_Label (1)
{
	page NumberSeriesPage
	page TermsOfDeliverPage (2)
}
1the menu is called com_log according to the module structure.
2new terms-of-delivery page

Authorization

Now the terms-of-delivery page is created and added to the menu, the application can be started to add authorizations and display the page.

Add page to Access Control List (ACL)

User can only access pages for which they are authorized. Open the ACL Type page and select object type Page. Press the import objects to add all Java Page files to the ACL object list. A message will confirm that a new ACL Object was added.

Add ACL Authorization

Open the ACL Role menu option and select the Page object types, and the TermsOfDelivery entry. Add new roles with maintenance rights.

intro entity to page acl

Refresh the web-page to see the added authorizations in the menus.

intro entity to page menu
Select the new page

The end-result is a fully functional page where terms-of-delivery can be added, modified and deleted.

At any time custom logic can be added using Java, simply by adding the custom keyword to entity, view-model, form, grid or page.

Generated Code

Every DSL component generates one or more source files. These generated files are located in a separate source folder and not directly relevant for the developer. None the less, it is good to have a general understanding of all the code, including generated classes.

The generated source code is clean, structured and easy to understand. In principle all these classes could also be written by hand. However, the DSL code-generator makes it far more convenient and less error-prone

The DSL essentially reduces the source-code of the system as a whole. Generated files are derived and not part of the source code and do not requiring maintenance.

This section goes through all the components mentioned in the Entity to Page section, and provides an illustration of the generated code.

Server side project

Add labels

The label-set generates two code files:

(1) a Java class with label constants.

These are used in code to reference labels. Using constants in stead of string-values, ensures that name-changes result in compilation errors.

Generated Code
public class TermsOfDeliveryLabels{

 public static final String CODE_LABEL = "codeLabel";
 public static final String DESCRIPTION_LABEL = "descriptionLabel";
 public static final String EX_WORKS_LABEL = "exWorksLabel";
 public static final String TRANSPORT_MODE_LABEL = "transportModeLabel";
 public static final String TRANSPORT_MODE_LABEL_A = "transportModeLabelA";
 public static final String TRANSPORT_MODE_LABEL_I = "transportModeLabelI";
 public static final String TRANSPORT_MODE_LABEL_R = "transportModeLabelR";
 public static final String TRANSPORT_MODE_LABEL_S = "transportModeLabelS";
 ...
}

(2) a TermsOfDeliveryLabels.properties file.

This file can be translated using standard Java I18N functionality.

codeLabel=Code
descriptionLabel=Description
exWorksLabel=Ex Works
transportModeLabel=Transport Mode
transportModeLabelA=Air
transportModeLabelA.icon=AIRPLANE
transportModeLabelI=Rail
transportModeLabelI.icon=TRAIN
transportModeLabelR=Road
transportModeLabelR.icon=TRUCK
transportModeLabelS=Sea
transportModeLabelS.icon=BOAT
...
Add value types

Every value-type generates a Java class, an abstract class (except for enumerates), and a JPA converter class.

(1) Java class

Generated Code
public class TermsOfDeliveryCode extends AbstractTermsOfDeliveryCode{(1)
	public TermsOfDeliveryCode(final String value) {
		super(value);
	}
}
1The abstract class implements the value-type, this concrete class only contains a constructor and can be moved to the manual source folder to add business logic.
Generated Code
public enum TransportMode implements DomainEnumerate{ (1)

	AIR(EnumerateItem.create("A", "transportModeLabelA")) ,
	RAIL(EnumerateItem.create("I", "transportModeLabelI")) ,
	ROAD(EnumerateItem.create("R", "transportModeLabelR")) ,
	SEA(EnumerateItem.create("S", "transportModeLabelS"))
	;

	public static EnumerateMeta meta = EnumerateMeta.create(
		AIR.item,
		RAIL.item,
		ROAD.item,
		SEA.item
	);
	...
}
1Java enum does not support inheritance, so there is only a concrete class

(2) The Converter class provides a mapping between the database value and the value-type.

Generated Code
@Converter(autoApply = false)
public class TermsOfDeliveryCodeConverter
	implements AttributeConverter<TermsOfDeliveryCode, String> {

  @Override
  @NonNull public String convertToDatabaseColumn(
  	@Nullable final TermsOfDeliveryCode attribute) {(1)

    if(attribute!=null) {
      return attribute.getValue();
    }
    return "";
  }

  @Override
  @NonNull public TermsOfDeliveryCode convertToEntityAttribute(
    @Nullable final String dbData) {(2)

    if(dbData!=null) {
      return TermsOfDeliveryCode.of(dbData);
    }
    return TermsOfDeliveryCode.EMPTY;
  }
}
1convert value type TermsOfDeliveryCode to a database string
2convert a database string to a TermsOfDeliveryCode
Add entity

The entity declaration creates a concrete entity class, an abstract entity class and two classes for setting operational- and user-fields.

Generated Code
@Access(AccessType.FIELD)
@Entity
@Table(name="com_log_termsofdelivery" (1)
,uniqueConstraints=@UniqueConstraint(columnNames={"code"}) (2)
)

public class TermsOfDelivery extends AbstractTermsOfDelivery{ (3)

	// Protected constructor required by Hibernate
	protected TermsOfDelivery(){
		super();
	}

	public TermsOfDelivery(
		@NonNull final TermsOfDeliveryCode code,
		@NonNull final Description description,
		@NonNull final TermsOfDeliveryLocation termsOfDeliveryLocation
		){

		super(
			code,
			description,
			termsOfDeliveryLocation
		);
	}
}
1Class name is converted into a database table name.
2The business key is converted into a uniqueness constraint
3The abstract class implements the entity, this concrete class only contains a constructor and can be moved to the manual source folder to add business logic.

The entity class is by default read-only, all the setters are contained in a specialized user class for user-fields, and update class for operational fields.

Add view-model
Generated Code
@ViewModelComponent
public class TermsOfDeliveryViewModel extends AbstractTermsOfDeliveryViewModel{(1)
}
1The abstract class implements the view-model, this concrete class is empty and can be moved to the manual source folder to add business logic.

Client side project

Add grid

The generated Java code uses a grid-builder to create the layout. Constants are added for all form-fields that can be referenced in custom page logic.

Generated Code
public class TermsOfDeliveryGrid{

	@NonNull public static final String CODE = "code";
	@NonNull public static final String DESCRIPTION = "description";
	@NonNull public static final String TRANSPORT_MODE = "transportMode";
	@NonNull public static final String TERMS_OF_DELIVERY_LOCATION =
		"termsOfDeliveryLocation";
	@NonNull public static final String EX_WORKS = "exWorks";

	public static <T> GridBuilder<T> create(final GridBuilder<T> builder)(1)
		throws ClientException{

		builder
		.add(CODE)
		.add(DESCRIPTION)
		.add(TRANSPORT_MODE)
		.add(TERMS_OF_DELIVERY_LOCATION)
		.add(EX_WORKS)
		;

		return builder;
	}
}
1the grid builder input parameter
Add form

The generated Java code uses a form-builder to create the layout. Constants are added for all form-fields that can be referenced in custom page logic.

Generated Code
public class TermsOfDeliveryForm{

	@NonNull public static final String CODE = "code";
	@NonNull public static final String DESCRIPTION = "description";
	@NonNull public static final String TRANSPORT_MODE = "transportMode";
	@NonNull public static final String TERMS_OF_DELIVERY_LOCATION
		= "termsOfDeliveryLocation";
	@NonNull public static final String EX_WORKS = "exWorks";

	public static <T> FormBuilder<T> create(final FormBuilder<T> builder)
		throws ClientException{

		builder
		.beginSection()
			.addField(CODE)
			.addField(DESCRIPTION)
			.nextColumn()
			.addField(TRANSPORT_MODE)
			.addField(TERMS_OF_DELIVERY_LOCATION)
			.addField(EX_WORKS)
		.endSection();
		return builder;
	}
}
Add page

The page declaration generates an concrete Java class with only a constructor, and an abstract Java class with the page logic.

Generated Code
@PageTitle("TermsOfDeliveryPage")
@Page
public class TermsOfDeliveryPage extends AbstractTermsOfDeliveryPage{

	public TermsOfDeliveryPage(
			@Autowired final PageService pageService,
			@Autowired final VComponentService componentService)
			throws ClientException{

		super(pageService, componentService);

		initialize();
	}

}

The abstract page class can extend any Vaadin page component.

Generated Code
@Route(value = "termsOfDeliveryPage", layout = VMainLayout.class)
@PermitAll
public class AbstractTermsOfDeliveryPage extends Div implements HasClientViewModel{
...
}

It declares the view-model and views.

Generated Code
protected final DefaultClientViewModel todViewModel;

protected final Component termsOfDeliveryGrid;
protected final Component termsOfDeliveryForm;

protected final AclAuthorization pageAuthorization;

and instantiates them in the constructor.

Generated Code
public AbstractTermsOfDeliveryPage(
	@Autowired final PageService pageService,
	@Autowired final VComponentService componentService) throws ClientException{

	todViewModel = pageService.createViewModel(
		TOD_VIEW_MODEL_NAME,
		getViewModelAuthorization(TOD_VIEW_MODEL));

	termsOfDeliveryGrid = createTermsOfDeliveryGrid();
	termsOfDeliveryForm = createTermsOfDeliveryForm();
}

finally the page is constructed using a page-builder.

Generated Code
protected void createLayout() throws ClientException{
	final VLayoutBuilder layoutBuilder = componentService.createLayoutBuilder();

	layoutBuilder
		.beginVertical()
			.addAndExpand(termsOfDeliveryGrid_
			.add(termsOfDeliveryForm)
		.endVertical();

	add(layoutBuilder.getComponent());
	setSizeFull();
}
Add menu
Generated Code
@Service
public class Menu_com_log{

	private final LabelProvider labelProvider;

	@Inject
	public Menu_com_log(final LabelProvider labelProvider) {
		super();
		this.labelProvider = labelProvider;
	}

	...

	public void addItems(
			final HasMenuItems hasMenuItems,
			final MenuSelectionHandler handler) {

		hasMenuItems.addItem(
			labelProvider.getLabel("COM_Labels.numberSeriesLabel")(1)
			.getDescription(),
			(e)->handler.openPage("numberSeriesPage"));

		hasMenuItems.addItem(
			labelProvider.getLabel("COM_Labels.termsOfDeliveryLabel")
			.getDescription(),
			(e)->handler.openPage("termsOfDeliveryPage"));

		hasMenuItems.addItem(
			labelProvider.getLabel("COM_Labels.uomLabel")
			.getDescription(),
			(e)->handler.openPage("uOMPage"));
	}
}
1Use of constants is not required as menus can not be sub-classed.

Core concepts

One language

An organization with limited resources is best served with the simplicity of one programming language. For Wercstat this means Java, both server-side and client-side. This simplifies training and hiring of developers.

Nonetheless, it is still possible to integrate with client-side languages or frameworks like JavaScript, Angular, Vue or React if required.

Less code

Less code means less hours spent on development and testing, less bugs, less legacy and less maintenance. In practice this means higher productivity, lower cost of ownership, better quality and agility.

But some code can not be avoided, in that case the best alternative is to generate code where possible. Wercstat generates code according to strict architectural patterns, improving code discoverability and preventing code duplication.

Generated code is not part of the application source-code, it does not need maintenance and can be automatically updated when required, for instance to include new technologies.

Business Developers

Wercstat supports both Business Developers and Technical Developers.

Business Developers use high level abstractions, comparable to 4GL languages, to implement business functionality. They do not need to know all the technical details of Java, only enough to code user requirements.

Technical Developers know the Java language in all its technical details. They develop more specialized requirements like web-services, custom UI components (e.g. plan-board) and interfaces with external systems (barcode-scanners, SCADA, etc.).

Everything is text

All application components are defined and stored in simple text files. This includes business-logic, entity definitions, user interfaces, external dependencies and documentation.

Benefits:

Future Proof

Text is the most open, accessible and future proof format.

Version Control

Database structures, dependencies and documentation are stored and versioned (GIT) together with the business code.

Code close to the business

Not only business entities are represented in code, but also primitive Java types (Boolean, String, BigDecimal, etc.) are replaced with business concepts like Confirmed, Document and Amount.

Benefits:

Better communication

The same business-terms (e.g. Price, Rate, OrderLine) are used both in the specification and the source code. This narrows the communication gap between developers and domain experts.

Error prevention

Improved type checking during development will catch errors early. A Price is different from an Amount, a CustomerReference is different from a Description. These differences are checked at development-time.

Compare this with Java primitive types, where all business-concepts are represented by the same set of primitive types and mistaking a document-code for a reference-number only become apparent when tests fail.

User interaction is business logic

Most user-interaction is handled on the server side, as part of the business logic. For example the visibility of fields and buttons, making fields editable or read-only, amending user input, setting field defaults, opening selection-screens, adding grid filters and showing error messages. All is handled server-side.

Benefit:

UI logic is business-logic, and implementing it on the server side makes it possible to change UI technology in the future, if needed.

Develop at all levels of abstraction

Wercstat allows developers to work at all levels of abstraction, from the Wercstat-DSL down to plain Java. Anything the Werstat DSL provides, can also be achieved using plain Java.

A good example are the abstraction levels available for building UI’s.

Abstraction level 4: UI DSL

Developers can create pages, grids, forms and menus using the extensive Wercstat-DSL.

The DSL internally uses Java UI-builders. These UI-builders are also available for developers if they need more control.

Abstraction level 3: UI builders

Developers can create pages, grids, forms and menus using UI-builders.

The UI-builders internally use Wercstat component-factories to create UI-components, and component-binders to connect these to the client-side view-model. These component-factories and component-binders are also available for developers if they need more control.

Abstraction Level 2: UI component-factories and component-binders

Developers can create pages, grids, forms and menus using component-factories and component-binders.

The Wercstat component-factories internally use Vaadin components, and the component-binders listen to view-model events to bind them together. These Vaadin components and client view-model events are also available for developers if they need more control.

Abstraction Level 1: UI Vaadin components and view-model events

Developers can create pages, grids, forms and menus can by using Vaadin components and connecting them to the view-model by listening to view-model events.

Abstraction Level 0: Custom solution

Developers can choose not to use the Wercstat framework and create a custom solution on top of the industry-standard Java and Spring stack.

Benefit:

The developer is not restricted in any way, and can choose the abstraction that matches the level of control needed.

Anywhere between 80% and 100% of the user interface will be created using the Wercstat-DSL. But for those special screens, which are important to the client, the UI can be customized at any level down to Javascript in the browser.

And of course, different levels of abstraction can be combined within a single UI page.

No null-pointer exceptions

Nullability

Nullability annotations are implemented throughout the framework, including generated code. This eliminates not only null-pointer exceptions but also the need to add null-checks or assertions in business code.

When using external libraries, null-pointer exceptions can still occur.
Entity Construction

Entity constructors, getters and setters have Nullability annotations for parameters and return values. If an entity field is mandatory, the getter will return a @NonNull value, and the setter will take a @NonNull parameter. And if the field is not calculated, a @NonNull parameter is added to the constructor to ensure that no instance of the entity can be created without a valid field value.

Benefit:

Null-pointer exception is the most prevalent error in Java applications. Eliminating it greatly improves the application stability and ultimately user experience.

Minimal vendor lock-in

Wercstat only depends on industry standard open source components which are widely used and supported by commercial companies.

Wercstat also aims to reduce its own vendor lock-in as much as possible. Most business logic can be developed with no dependency on the Wercstat framework.

Entities, repositories and value-types depend only on the com.wercstat.domain submodule. This dependency can be easily replaced by bespoke code or libraries from other vendors.

The user interaction (UI) logic is more dependent on the framework. Replacement with other UI solutions is manageable, but will need effort depending on the target platform.

And of course, everything is text, no proprietary formats or structures stored in obscure database tables.

Benefit:

A lower threshold to start using the Wercstat framework, and the future ability to switch should the need occur.

Commercial Support

Wercstat is a complex framework for a specialized market. Stability, continuity and innovation is essential for customers.

The fact that Wercstat is a commercially viable product, has a loyal customer base and has a sixteen year track-record, guarantees continuity and long term support.

Benefit:

Wercstat systems will stay up-to-date with modern technology. No more big-bangs or application rewrites.

Wercstat-ERP

Overview

The example application presented here is Wercstat-ERP and is built using the Wercstat-Framework. Over time this application will replace a legacy InforLN ERP system by incrementally moving functional modules from InforLN to Wercstat-ERP. During this process a robust integration with the existing ERP system is key.

Functionality

Central to the application are the order entry and planning modules.

Sales orders are created automatically based on either B2B messages, or PDF order-confirmations received by email. If needed sales representatives can amend the order, with the PDF and mail information available in Wercstat-ERP.

Once the order is confirmed it is forwarded to the legacy ERP system, and a work-preparation document is created in Wercstat-ERP.

The work-preparation document contains tasks for the planning of materials, production-capacity, outsourcing and transport. Once the planning is completed, including inter-company purchases, the sales representative is informed by email. The email contains the planning details and a direct HTTP link to the sales order in Wercstat-ERP.

intro diagram wercstat inforln

When the module was first build most tasks were completed manually by end users. Over time more and more tasks are handled by background jobs, based on lead-time calculation rules.

All sales and purchase orders, together with the production planing, are fed into the legacy InforLN ERP system.

The Wercstat DSL was extended to generate InforLN application interfaces (API’s).

The starting page is configured per user-group. Forklift drivers for example start with the scanning page on their mobile device:

overview real scan page

Office users start with the workflow task screen:

overview real roles page

Desktop pages have a collapsible section on the left with the roles of the user.

Next is the workflow page with on the left a list of all teams that the user is a member of, and finally a list with pending tasks.

Double-clicking a task-line of the customer support team will open the sales order.

overview real so

All fields are editable and the confirm button is available. This changes the moment the order is confirmed.

overview real so confirmed

Once the sales order is confirmed, the work preparations starts and workflow-tasks are created. The metal waiting task is the result of input material that has to be purchased inter-company.

overview real wp metal waiting

Double clicking the metal waiting task will open the task page.

overview real wp metal waiting task

The material task is linked to a purchase order. Selecting the Open Order button displays the purchase order.

overview real wp metal waiting task po

As this is an inter-company purchase, the order generates a sales order in the supplying company. Selecting the Target Order button displays the inter-company sales order.

overview real wp metal waiting task po so

Subsequently, in the supplying company a new work-preparation process is started, with all the tasks required to produce the requested material.

overview real wp metal waiting task po so wp

Implementation

Wercstat-ERP consists of nine main modules with strict uni-directional dependencies. For example, module production can refer to materials or finance, but can not reference module trade.

intro diagram structure wercstat erp

Projects

The Wercstat-ERP main modules are implemented as Eclipse code-projects

intro structure project

There are additional Eclipse projects for documentation (ascidoc) and deployment (maven).

Folders

Every Eclipse project has source-folders for manual Java-code (.java files), generated Java-code and domain-model files (.real files).

intro structure folder
Source folderContent

src/main/java

business logic

src/main/real

domain model

src/test/java

automated tests

src/generated/java

database and UI Java code, derived from the domain model

Wercstat automatically generates all Java code for database and UI interaction, based on the domain model. The generated code is placed in the src/generated/java package, outside the view of the developer.

Domain Model

The domain model is structured around organizational modules and submodules. It consists of .real files which define entities, forms, grids, pages, menu’s, etc.

intro structure real

Packages

The Java code automatically follows the domain model structure.

intro structure java

Tables

The same applies to the automatically generated database tables:

intro structure table

The end-result is a consistent ERP application where business logic is clearly structured and maintainable, even when it grows to hundreds of entities.

Although discouraged, you can use alternative table and table-field names.

Code Structure

Modules

Wercstat-ERP packages have the following structure:

com.wercstat.erp.server.<module>.<submodule> or

com.wercstat.erp.client.<module>.<submodule>

for example com.wercstat.erp.server.trd.req and com.wercstat.erp.client.prd.pln

Every sub-module has a fixed set of sub-packages and classes.

overview architecture module api
Public module API

The main sub-package of every module exposes the public interface (API).

This includes sub-packages:

  • entity : contains all module entities

  • extension : contains interfaces that module entities can implement

  • view : contains all module view-models

and the following public classes:

  • TRD_REQ_CommandApi : is a facade to module business functionality where data is updated

  • TRD_REQ_QueryApi : is a facade to module business functionality where data is read

  • TRD_REQ_Constant : public module constants

External modules can only access packages or classes from the public API.

Private module implementation

The internal sub-package contains the actual implementation of business logic and is not accessible from outside the module.

As in the public API (TRD_REQ_CommandApi, TRD_REQ_QueryApi) the internal business logic is divided into a read and write sub-package.

overview architecture module internal
Read / Write separation

Write methods update the database and can have side-effects in the system. They often contain the majority of the critical to business logic.

Read methods have no side effects and often contain less business logic.

By separating updates from reads (and commands from queries), it is easier to reason about the interaction between modules.

For example: finding modules that call write methods in sub-module prd.wpr.

With Eclipse search functionality it is easy to see that update methods in the sub-module prd.wpr are only called from sub-module trd.req and prd.irr.

overview architecture command reference

In this case that makes sense because only sales orders (trd.req) and production irregularities (prd.irr) can update work preparation (prd.wpr).

For example: see what modules are updated by business methods.

Considerer the supporting services that the following Java class declares:

@Service
public class MRequestLineActionWriter {

	private final DOC_COM_CommandApi doc_com_CommandApi;
	private final PRD_WPR_CommandApi prd_wpr_CommandApi;

	private final COM_PAR_QueryApi com_par_QueryApi;
	private final PRD_BAS_QueryApi prd_bas_QueryApi;
	private final PRD_WPR_QueryApi prd_wpr_QueryApi;
	...

}

It is instantly clear that this method only updates the doc.com and prd_wpr sub-modules.

Again in this case it makes sense, because the class contains actions from the sales line web-page. And the sales line calls doc.com for a new sales order number (which updates the number-series), and calls prd.wpr to start work-preparation.

Should there be any other CommandApi services, then this might indicate architectual issues.

Rule enforcement

Wercstat-ERP enforces the architectual rules using archunit ( https://www.archunit.org/ ). Part of these rules are automatically generated by the Wercstat-ERP module structure.

And for general code quality, ErrorProne ( https://errorprone.info/ ) static checks have been added to the build process.

Order Entry Page

One of the UI pages in the trade project, is used for sales-order entry:

intro application
Figure 3. Order Entry Page

This page manages content form five different database tables:

  1. Sales Order

  2. Sales Order Line

  3. User Comments

  4. Mail Message

  5. Mail Message Attachments

All interaction between the UI components, and the interaction with the server-side view-models is handled by the Wercstat framework.

Form

The top left form in the Order Entry Page contains the order-header:

client request form1

The complete form declaration using the DSL is as follows:

form MRequestForm
{
	viewmodel MRequestViewModel

	row header {
		label-code mRequestLabel
		value documentCode
		value documentDate
		field company
		value company/description
		field requestType
		field biRequestStatus
	}

	field partner [name]

	field referenceA
	field referenceB

	label requestType
	row {
		value requestType
		value requestType/description
		field currency
	}

	field requestedDeliveryDate
	field plannedDeliveryDate

	label termsOfDelivery
	row {
		value termsOfDelivery
		field transportMode
	}

	value-label termsOfDelivery/termsOfDeliveryLocation
	row {
		value deliveryAddress
		value deliveryAddress/partner/name
	}
	tab value deliveryAddress/name1
	tab row {
		value deliveryAddress/zipcode
		value deliveryAddress/street
	}
	tab row {
		value deliveryAddress/city
		value deliveryAddress/country/description
	}

	next-column

	field quotation [description]
	field project [description]
	field contract [description]

	label lmePrice
	row {
		value lmePrice
		value lmeCurrency
	}

	field representativeInt [name]
	field termsOfPayment [description]
	field warehouse [description]
	field baanOrno

	label invoiceAddress
	row {
		value invoiceAddress
		value invoiceAddress/partner/name
	}
	tab value invoiceAddress/name1
	tab row {
		value invoiceAddress/zipcode
		value invoiceAddress/street
	}
	tab row {
		value invoiceAddress/city
		value invoiceAddress/country/description
	}

}

The form declaration uses the following key-words:

row { <fieldList> }

marks a list of fields which are displayed on the same row

label <fieldName>

display the label of view-model field <fieldName>

value <fieldName>

display the value of a view-model field <fieldName>

field <fieldName>

display both the label and the value of a view-model field <fieldName>

tab

skip the label or value column

Header Row

Let’s look in more detail, beginning with the header row:

client request form2

defined as follows:

row header { (1)
	label-code mRequestLabel (2)
	value documentCode (3)
	value documentDate (4)
	field company (5)
	value company/description (6)
	field requestFlow (7)
	field biRequestStatus (8)
} (9)
1start a list of fields and labels that are displayed as one row.
2display the label with code mRequestLabel. This will display Request.
3display the value of the documentCode field. This will display SR0000803.
4display the value of the documentDate field. This will display 24-02-2021.
5display the company field. This will display Company and ROE.
6display the value of the company/description field. This will display Roermond BV.
7display the requestFlow field. This will display Flow and Sales Order.
8display the biRequestStatus field. This will display Status and Draft.
9end the list of row fields
Left Column
Part 1

Next the first part of the left column:

client request form3
field partner [name] (1)

field referenceA (2)
field referenceB

label requestType (3)
row { (4)
	value requestType
	value requestType/description
	field currency
}

field requestedDeliveryDate (5)
field plannedDeliveryDate

label termsOfDelivery (6)
row {
	value termsOfDelivery
	field transportMode
}
1Display the partner field, followed by partner/name. This will display label Customer, the customer input field and the customer name. The square brackets [] are a special short-cut making it easier to add foreign-entity fields.
2Add the refrenceA label and value.
3Add only the label requestType.
4Start a row of fields, containing the value of requestType, the value of requestType/description, and the label and value of currency.
5Display the label and value of requestedDeliveryDate and plannedDeliveryDate
6Add label of field termsOfDelivery, and display a row with the value of termsOfDelivery and both the label and value of transportMode.
Part 2

The second part of the left column simply displays an address:

client request form4
value termsOfDelivery/termsOfDeliveryLocation (1)
row { (2)
	value deliveryAddress
	value deliveryAddress/partner/name
}
tab value deliveryAddress/name1 (3)
tab row { (4)
	value deliveryAddress/zipcode
	value deliveryAddress/street
}
tab row { (5)
	value deliveryAddress/city
	value deliveryAddress/country/description
}

next-column (6)
1termsOfDelivery/termsOfDeliveryLocation displays the location belonging to the terms of delivery.
2add a row with the deliveryAddress input field, and partner-name belonging to the address
3display name field of the delivery address itself.
4display zipcode and street of the delivery address.
5display city and countr/description of the delivery address.
6start a new column

The right column uses all the same concepts and should be self explanatory.

Label and value subcolumns

Note that columns are subdivided into a label sub-column and a value sub-column. This allows easy alignment of field-values and make the form easier to read.

For example:

field referenceB

displays the label in the label-column, and field value in the value-column:

client request field1
label termsOfDelivery
row {
	value termsOfDelivery
	field transportMode
}

displays the termsOfDelivery label in the label-column, and the field termsOfDelivery and transportMode in the value-column:

client request field2
tab row {
	value deliveryAddress/zipcode
	value deliveryAddress/street
}

skip the label-column, and displays zipcode and street in the value-column:

client request field3
Generated Code

The generated code is more verbose, but still easy to understand:

Generated Code
public static <T> FormBuilder<T> create(final FormBuilder<T> builder) throws ClientException{

	builder
	.beginSection()
		.beginCell(FormCellType.HEADER)
			.addValueReadOnly(REQUEST_TYPE_MTYPE)
			.addValue(DOCUMENT_CODE)
			.addValue(LINKED_DOCUMENTS)
			.addValue(DOCUMENT_DATE)
			.addField(COMPANY)
			.addValue(BI_REQUEST_STATUS)
		.endCell()
		.addField(PARTNER,"name")
		.addLabel(REFERENCE_A_LABEL)
		.beginCell(FormCellType.LINE)
			.addValue(REFERENCE_A)
			.addValue(REFERENCE_WARNING)
			.cssClass("real-field-warning")
		.endCell()
		.addField(REFERENCE_B)
		.addLabel(REQUEST_TYPE_LABEL)
		.beginCell(FormCellType.LINE)
			.addValue(REQUEST_TYPE)
			.addValue(REQUEST_TYPE_DESCRIPTION)
			.addField(CURRENCY)
		.endCell()
		.addLabel(REQUESTED_DELIVERY_DATE_LABEL)
		.beginCell(FormCellType.LINE)
			.addValue(REQUESTED_DELIVERY_DATE)

		...
}

Deployment

Wercstat-ERP has so called xcopy deployment, which means you can simply copy application files to a server-directory and start the application. Providing the server has Java runtime-edition installed.

The directory only needs two files; the application itself erp-trade-client-1.6.7.jar and a file with settings application.properties.

Create the JAR artifact

The Maven POM has a special production profile for deployment in production environment.

mvn install -Pproduction

This profile adds code optimization and packaging for front-end artifacts. Execute the trade-client-install (production) launch to create the deployment Java archive file (JAR-file).

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for ERP Parent 1.6.7:
[INFO]
[INFO] ERP Parent ......................................... SUCCESS[  0.235 s]
[INFO] ERP Shared ......................................... SUCCESS[  0.503 s]
[INFO] ERP System Server .................................. SUCCESS[  7.656 s]
[INFO] ERP Common Server .................................. SUCCESS[  6.975 s]
[INFO] ERP Catalog Server ................................. SUCCESS[  4.912 s]
[INFO] ERP Document Server ................................ SUCCESS[  3.044 s]
[INFO] ERP Finance Server ................................. SUCCESS[  1.409 s]
[INFO] ERP Inventory Server ............................... SUCCESS[  3.512 s]
[INFO] ERP Quality Server ................................. SUCCESS[  1.263 s]
[INFO] ERP Warehousing Server ............................. SUCCESS[  1.389 s]
[INFO] ERP Material Server ................................ SUCCESS[  4.173 s]
[INFO] ERP Production Server .............................. SUCCESS[  9.909 s]
[INFO] ERP Quality Server ................................. SUCCESS[  0.026 s]
[INFO] ERP Trade Server ................................... SUCCESS[  4.918 s]
[INFO] ERP Planning Server ................................ SUCCESS[  2.876 s]
[INFO] ERP Trade Batch .................................... SUCCESS[  0.957 s]
[INFO] ERP System Client .................................. SUCCESS[  2.665 s]
[INFO] ERP Trade Client ................................... SUCCESS[ 10.940 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

The Wercstat-ERP version number is automatically added to the JAR-file name, for example erp-trade-client-1.6.7.jar.

Deploy the JAR artifact

Normally every server has a fixed application.properties file with specific settings, including the database connection. Deploying the application is as easy as copying the compiled artifact (the .jar file) and starting it from Java:

java -jar erp-trade-client-1.6.7.jar & (1)
1the & parameters starts the application in the background

The following banner is displayed on start-up:

__          __               _        _     ______ _____  _____
\ \        / /              | |      | |   |  ____|  __ \|  __ \
 \ \  /\  / /__ _ __ ___ ___| |_ __ _| |_  | |__  | |__) | |__) |
  \ \/  \/ / _ \ '__/ __/ __| __/ _` | __| |  __| |  _  /|  ___/
   \  /\  /  __/ | | (__\__ \ || (_| | |_  | |____| | \ \| |
    \/  \/ \___|_|  \___|___/\__\__,_|\__| |______|_|  \_\_|

Monitoring

The application is monitored in three ways:

  • Server-logging (Log4J) for normal logging and exception stack traces

  • Health monitoring and metrics gathering (Spring Boot Actuator)

  • Cloud exception monitoring (bugSnag)

  • Custom monitoring

Server-logging and health monitoring are build-in features of Spring Boot.

Cloud Monitoring

Wercstat supports cloud monitoring, in this case BugSnag ( https://www.bugsnag.com/ ), but any service can be integrated.

All exceptions in Wercstat-ERP are sent to the cloud with information about the server, the user and a complete Java stack trace.

overview monitoring bugsnag trace

The overall occurrence of errors over time is monitored:

overview monitoring bugsnag

Support staff can be informed of errors by email based on specific business-rules. For example, the type of exception, the frequency of the exception or the server where the exception occurred.

Custom Monitoring

Custom monitoring is additional option during testing of production. For Wercstat-ERP, a list of "sanity checks" run on a fixed schedule and failures are automatically mailed to support staff.

overview monitoring custom

Integration Test

With almost weekly deployments to operational, the Wercstat-ERP application has hundreds of integration tests to ensure the stability of existing functionality.

These test rely on an in-memory database that is created at the start of every test. To assist in the creation of master test-data, the DSL can create an import-writer class for entities.

These classes have setters and constructors with primitive types in stead of value-types and entities.

@Service
public class TestDataImport_com_log implements DatabaseInitService {

	@Autowired private COM_LOG_ImportWriter com_log_importWriter;

	@Override
	public void populate() {

		// All Modes
		com_log_importWriter.addTermsOfDelivery(
			TERMS_OF_DELIVERY_EXW,
			"Ex Works",
			PLACE_OF_DELIVERY)
		.user().setExWorks(ExWorks.TRUE);

		com_log_importWriter.addTermsOfDelivery(
			TERMS_OF_DELIVERY_FCA,
			"Free Carrier",
			PLACE_OF_DELIVERY);

		com_log_importWriter.addTermsOfDelivery(
			TERMS_OF_DELIVERY_CPT,
			"Carriage Paid To",
			PLACE_OF_DESTINATION);
		...

		// Sea
		com_log_importWriter.addTermsOfDelivery(
			TERMS_OF_DELIVERY_CFR,
			"Cost and Freight",
			PORT_OF_DESTINATION);

		com_log_importWriter.addTermsOfDelivery(
			TERMS_OF_DELIVERY_CIF,
				"Cost, Insurance and Freight",
				PORT_OF_DESTINATION)
		.user().setTransportMode(SEA);
		...
}

InforLN Interface

To integrate with InforLN ERP the DSL is extended to declare external tables.

external-table Baan_tdlra011 baanIV external-name "tdlra011" read custom-reader{

	sha key short koor // Order Type  (1) (2)
	sha key int orno   // Order Number
	sha key short pono // Order line

	sha date t_date    PlanDate	(3)
	sha short kotr     // Transacation Type

	sha int leno        // IdNo
        string cprj 6	// Project
        string item 16	SubstrateCode
	sha string dset 11	ItemCode

        short wknr      // Week number
        short ittc      // 1=Standard Item Type
	...
}
1key fields are part of the business key
2sha field are included in a checksum calculation to detect changes
3field t_date is directly mapped to value-type PlanDate

The DSL can generate reader and writer classes for the external database including all required type conversion.

The DSL can be extended to support other legacy systems through database- or web-interfaces.