The clean architecture principle can be followed quite nicely in the model. Below is a simple example of the clean architecture in studio.

Domain LayerApplication LayerAdapter LayerInterface Layer
DomainDomainDomainDomain
EntitiesEntities
Entities
AttributesAttributes
Attributes
Relations

Relations
Static instances





Validation rules
InteractionInteractionInteractionInteraction

Flows


Event


Page
Page

Container
Container



Button



Text items



Content items



Images



Validation types
Business RulesBusiness RulesBusiness RulesBusiness Rules
Value lists


Validation rules


Expressions


Decision tables


Rule groups


ConnectivityConnectivityConnectivityConnectivity


Service typesService types


Schema set SOAP service


Domain schema REST service
ConfigurationConfigurationConfigurationConfiguration


Data mappingWeb service

This chapter of the design guides supplies you with a small practical examples in Blueriq. For more elaborate examples, see 5. Use Cases.

Small Example

In this small example a person is able to rent a house. In the domain layer we define the entities and attributes for Person and for House. There exists a 1:n relation between them, as a person might rent multiple houses. A person has a salary, and based on that his or her maximum rent is calculated.

Everything defined so far does not say anything about how we get this information, or how it is presented to the end user or persisted in a database. This domain and business rule is part of the Domain Layer.









We now create the use case that a person looks at available houses, chooses one and starts renting it. The flow starts with a page that shows all houses that a person can rent, based on his or her maximum rent (which is determined by the salary). After a choice has been made, the flow continues to store the information in the database, and shows a thank you page. This flow describes a use case, and is part of the Application Layer.

You might wonder why the sub-flow for the database is referenced here in the application layer. Storing something in the database should be part of the Interface Layer. While we indicate here when information should be stored, we do not indicate any specifics of where to store the information or how to save it exactly. We just indicate the moment when information should be saved. For the first version, the sub-flow might be completely empty (a mock-flow).




The service that stores the updated rental information in the database is part of the interface layer. The database could actually be behind a web service, when the service is of type AQ_WebServiceClient.

As this service is placed in a sub-flow, we can simply change out this manner of persisting with a complete different one. This would not affect the application or domain layer. If the service would have been placed directly in the flow for renting a house (see Application Layer), then all use cases in which data is stored need updating when the persistence manner changes.

You might place the StoreInDatabase flow in a library, together with all other persistence flows. If you have a second library with the same flows for a different type of persistence, you could simple swap out the library for one-another and do not have to make any changes to the rest of application in order to store the information somewhere else. This is in essence similar to dependency injection, but requires you to adhere to a strict naming of the re-usable flows, and any element in the library for it to work, and is not often done in practice.

For more elaborate examples, see 5. Use Cases.





The need for decoupling

The clean architecture works well for any programming language. Once the domain layer is defined, the application layer can build on that. The internals of the domain layer are placed in methods, and the methods can be used in the application layer. A method in above example could be registerNewRenter(Person person) of the House class. What exactly is happening inside that method is hidden from the application layer. Only when the footprint of the method changes does this have impact on all classes in the application layer that use the method. Luckily do footprints of methods change much less frequently than the internals of the method.

In Blueriq is everything included in an interaction module. This means that your flow has access to each and every element in the domain of the domain layer. This is in principle correct, and adheres to the dependency rule of the clean architecture. What this however implies, is that changing any element of the domain layer may have an impact on the application or interface layer. This was not your intention however, as you also expose internals to the higher layer. In any object oriented programming language, you expose a method or interface to another class, but not the inner working of the method. In Blueriq there are no methods, meaning that all internals are exposed, and can be seen as method or interface. Even though this adheres to the dependency rule, this is much more than intended or needed. As a result, before changing any small thing an impact analysis needs to be performed. This can be achieved by using the dependencies in studio. Having to do this for every element is however a tedious job, and mistakes are bound to happen. This leads to:

  • Additional work to check for the impact of every change
  • People being scared to make changes
  • A preference for creating new attributes above reusing/refactoring old ones, cluttering the domain
  • An overflow of elements in studio, which reduces the working speed and increased the mental load

It would be great however if you can know for sure what elements can be changed without any impact on the higher layers, and which ones should be changed with care after an impact analysis. What we essentially want to achieve is that only a small set of elements has this dependency (you can also say that it is coupled), and you can quickly and easily change all other elements. This small set of elements can be regarded as the method footprint if you think of it as code. This leads to

  • Quicker development
  • Better test-ability
  • Confident business engineers

The core of Blueriq is its knowledge modelling capabilities. That is what most time is spent on, and delivers most value to our customers. This functionality is in the domain and application layer. We have to integrate to external systems, but that is all abstracted away in the model, with a simple service type. There are also no details on how the front-end is creating a running page, only an abstract definition of what should be placed on a page. So you envision the outer two layers, the interfaces and adapters layer as rather thin in the Blueriq model, as shown on the right.

When a layer boundary is crossed, then this is a good opportunity for decoupling. The adapters layer sole responsibility is to decouple the center of the onion with outer layer. As the center of the onion is so large in Blueriq, there actually is also a need for decoupling when staying in the same layer. For example, lets say a calculation for the maximum lease amount of a car is dependent on the risk score of a customer. Both of these numbers are part of the domain layer. You may however want to decouple each calculation so that all internals of a single calculation may be refactored and maintained easily, and are not 'in the way' when working at other parts of the application. So, to summarize, the application of decoupling can be split up into two categories:

  1. Decoupling to prevent layers blend into each other.
  2. Decoupling to expose functions within the same layer.

The next section of this guide describes concrete patterns of how this can be achieved: 4. Patterns

Sidenote: Creating custom code

When creating a project with Blueriq for a customer it is often not avoidable to create custom code. The minimum is the styling of the runtime for the specific customer, but you also can create custom containers and services and other components. Using the notion of layers in the clean architecture, we can nicely plot where the custom code should be, and where it should be located.

In the Applications and Domain Layer no custom code should be found. These are all functions that Blueriq supports out-of-the-box. If the functionality in these layers us not sufficient, then a feature request should be created to enrich the software. Any custom code in these layers is undesired.

In the Interface Layer the desired custom code is found. These are connections to external (legacy) systems or databases that are customer specific and that will never be supported in the product.



Typical example: A Blueriq Project Structure

In Blueriq it is possible to stack modules on each other to reuse elements from lower modules. This enables the use of generic functionality in lower modules, and specializing them in a specific module if changes are needed. These modules should represent business concepts, such as a product being sold. In below structure, the middle layer represents different labels of the company which are similar to each other in most aspects, and therefore use the same generic library. Each of these labels is represented to the end-user using a certain channel. Examples of such channels can be a website or an app on the phone or tablet. For each channel you want to change the behavior of the application slightly so that the user experience is changed to optimally function for that specific channel. This represents the top layer of the structure.


The underlying idea why this structure is agile and maintainable is that you can perform changes on the level that fits the change. In case that Label C has a slight variation on the rules, then you would create that logic in the Label C module. In case you want to split up your pages for Channel 2 (as that might be a mobile channel), then you change your flow in the Channel 2 module. If a change should be applied to all applications, then you perform it in the generic base module. When making changes you should consider when functionality should be moved up or down the module structure. You should make functionality more generic (moving down) when similar functionality is implemented in horizontally aligned modules. You should make functionality more specific (moving up) when you notice that each upper module changes the functionality significantly. A common pitfall is to create functionality in the generic layer while it is not generic (yet).

The advantage of this is that small changes could be implemented quickly. The disadvantage of this approach is an undesirable growth of the generic layer which leads to a high number of dependencies through the application landscape.

Small changes usually are:

  • New label with small minor changes. A new module in the middle layer can be added based on the generic module.
  • New channel with small minor changes. A new module can be added to the top layer for the corresponding label.
  • New functionality within a known channel and label. Change the functionality in the corresponding module and consider if the new functionality causes refactoring to make it more generic or specific.