Taking inspiration from the Vaadin Bakery app, we decided to write a full stack demo that simulates the orders management of a real bakery online store.

The goal of the project is to show how to create a graphical interface built through the new Holon Vaadin Flow module and in general how easy it is to develop a complete application through the Holon platform.

Inside the post we will analyze the main features of the application, then we will go deeper inside the project viewing the various application layers and the building blocks that we used to write the application code.

Introduction: features, application layers and building blocks

Here’s the main features we provided:

  1. Backoffice function to insert and manage users
  2. Backoffice function to insert and manage bakery products
  3. Storefront function to manage the whole lifecycle of the orders
  4. Analysis function on sales data through dynamic graphs

The Bakery application’s software is divided into three main common logical layers, that are:

  • Presentation layer
  • Business logic layer
  • Data model layer

The building blocks used to implement each of the layers are:

The full code of the demo is available on GitHub at https://github.com/holon-platform/holon-vaadin-flow-demo.

Let’s code: project setup and code

Project setup

The first thing we need to do is setup the Bakery application. We use Maven to manage it through the pom file. We need to import the Holon Platform BOM; this will manage all Platform dependencies ensuring to have the right version of each artifact.

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>com.holon-platform</groupId>
			<artifactId>bom</artifactId>
			<version>${holon.platform.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
    </dependencies>
</dependencyManagement>

We need two specific modules of the platform so the <dependencies> section will be populated with the following modules:

<!-- Holon JDBC Datastore with HikariCP starter -->
<dependency>
    <groupId>com.holon-platform.jdbc</groupId>
    <artifactId>holon-starter-jdbc-datastore-hikaricp</artifactId>
</dependency>

This is the Holon JDBC Datastore Spring Boot starter, used to automatically configure the data access layer.

<!-- Holon: Vaadin starter -->
<dependency>
    <groupId>com.holon-platform.vaadin</groupId>
    <artifactId>holon-starter-vaadin-flow</artifactId>
</dependency>

The second is Holon Vaadin Flow starter that represents the platform support for the Vaadin Flow (10+). To complete the <dependecies> section we will define:

<!-- H2 -->
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.197</version>
</dependency>

the H2 in memory database dependency;

<!-- Vaadin Board add-on -->
<dependency>
    <groupId>com.vaadin</groupId>
    <artifactId>vaadin-board-flow</artifactId>
    <version>2.2.0</version>
</dependency>

<!-- Vaadin Charts add-on -->
<dependency>
    <groupId>com.vaadin</groupId>
    <artifactId>vaadin-charts-flow</artifactId>
    <version>6.2.2</version>
</dependency>

a couple of Vaadin addons to use Charts and Board components.

Project setup is completed through the application.yml file:

holon:
  datasource:
    url: "jdbc:h2:mem:test"
    username: "sa"
        
server:
  port: 8080

This will auto-configure a Holon JDBC Datastore, that will be available and accessible as a Spring Bean and will also configure an embedded Tomcat that will listen HTTP connections through port 8080.
Database will be created and populated in memory at startup setting schema.sql and data.sql files.

Configuration

Now that the project setup is complete, we can give a brief overview of the main aspects from the Java development point of view.
Let’s start from the Spring entry-point, that is the Application class.

@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

	@Bean
	public Realm realm(AccountProvider accountProvider) {
		return Realm.builder()
				// add an Authenticator using default Account interface and given AccountProvider
				.withAuthenticator(Account.authenticator(accountProvider))
				// use the default Authorizer
				.withDefaultAuthorizer().build();
	}

	// Account provider
	@Bean
	public AccountProvider accountProvider(Datastore datastore) {
		return userId -> datastore.query().target(User.TARGET).filter(User.EMAIL.eq(userId)).findOne(User.USER)
				// map the user PropertyBox to an Account
				.map(user -> Account.builder(userId)
						// details
						.withDetail(User.USER_DETAIL_NAME, user.getValue(User.NAME))
						.withDetail(User.USER_DETAIL_ID, user.getValue(User.ID))
						// build user stored credentials declaring SHA-512 as hash algorithm
						.credentials(Credentials.builder().secret(user.getValue(User.PASSWORD)) // password
								.hashAlgorithm(Credentials.Encoder.HASH_SHA_512) // hash algorithm
								.base64Encoded().build() // secret is Base64 encoded
						)
						// load permissions
						.permissionStrings(user.getValue(User.ROLE)).build());
	}

	@Bean
	@SessionScope
	public AuthContext authContext(Realm realm) {
		return AuthContext.create(realm);
	}

	@Bean
	@SessionScope
	public LocalizationContext localizationContext() {
		LocalizationContext lc = LocalizationContext.builder().withInitialLocale(Locale.US).build();
		lc.localize(Localization.builder(Locale.US).defaultDecimalPositions(2).build());
		return lc;
	}

This class is used to auto-configure the application and run the application itself, that will be deployed to Tomcat.
This will auto-configure a Holon JDBC Datastore, that will be available and accessible as a Spring Bean and will also configure an embedded Tomcat that will listen HTTP connections through port 8080.
For simplicity, we decided to concentrate, in the same class, the part relating to the configuration of the platform. It was enough to define the following Spring Beans to configure authentication and authorizations:

  • Realm: the main entry-point for authentication and authorization
  • AccountProvider: used to provide Account instances using account id
  • AuthContext: an optional Bean (Session scoped) used to facilitate the representation of the current authentication and authorization context

We also defined the LocalizationContext that is the main entry-point for the internationalization architecure of the platform, enabling localization of messages, numbers and date/time.

So let’s quickly recap: what we have so far is a pom, a Yaml file and a Java class. That’s all folks: all the configuration we need is contained in these three objects. Now we can dedicate ourselves to the implementation of the user interface.

Application coding

We don’t want to go into the implementation details: the most important thing is to underline how easy and fast it is to build the graphical components of our interface through the Holon platform. The idea is to start from the Product entity and from this entity build the insertion / modification form and the data visualization table.

The data model

The Product interface is the representation of our product database entity:

public interface Product {

	public static final NumericProperty<Integer> ID = NumericProperty.create("id", Integer.class);
	public static final StringProperty NAME = StringProperty.create("name").message("Name")
			.withValidator(Validator.notBlank(Localizable.builder().message("Product name is required").build()));
	public static final NumericProperty<Double> PRICE = NumericProperty.create("price", Double.class).message("Price")
			.withValidator(Validator.notNull(Localizable.builder().message("Product price is required").build()));

	public static final PropertySet<Property<?>> PRODUCT = PropertySet.builderOf(ID, NAME, PRICE).identifier(ID)
			.build();
	public static final DataTarget<?> TARGET = DataTarget.named("products");

}

Check the documentation to learn more about the main aspects of the Holon platform Property model.

UI: form and table

To manage the informations about products we need an input/edit form and a table to view the list of data. This is the snippet of code to create the input form to populate and manage products table:

PropertyInputForm form = Components.input.form(Product.PRODUCT).hidden(Product.ID).build();

Starting from the Components interface, that is the main provider of UI components of the platform, with this single line of code we get a form whose input prompts are automatically created based on the fields of the Product property model. In this case we also removed the Product ID from the form through the hidden(Product.ID) method.
All the saved products are present in the table thus constructed:

Components.listing.properties(Product.PRODUCT).fullSize().selectionMode(SelectionMode.SINGLE)
		.visibleColumns(Product.NAME, Product.PRICE).resizable(true)
		.withThemeVariants(GridVariant.LUMO_ROW_STRIPES, GridVariant.LUMO_COLUMN_BORDERS)
		.dataSource(datastore, Product.TARGET).withQueryConfigurationProvider(this)
		.withDefaultQuerySort(Product.NAME.asc())
		.renderer(Product.PRICE, new TextRenderer<>(item -> "$" + lc.format(item.getValue(Product.PRICE), 2)))
		.withItemClickListener(evt -> {
			enableForm(true);
			form.setValue(evt.getItem());
			btnInsertUpdate.setText("Update");
		}).build();

This is the product table which is a single-selection table, with two visible and resizable columns. The target of the table dataset is the product entity and we set this with the datasource(datastore, Product.TARGET) method. We have set a default sort on the product name field and we also rendered the price column with a TextRenderer.
The single click on a table row will trigger a listener with actions defined inside the lambda of the withItemClickListener(evt ->…) method.So a table with all these features will be the result of this single fluent instruction provided by the Holon platform builders.

This is a screenshot of the result:

Summary

We’ve seen how to setup the Bakery demo project, the main configuration and some snippet of code taken from the presentation layer to underline how quickly and easily you can develop a full stack application through the Holon platform. Looking at the source code of the application at https://github.com/holon-platform/holon-vaadin-flow-demo you’ll find the complete code to see the the newly created Holon Vaadin Flow module in action to build all the user interface.

Multi-tenancy is a fundamental architecture which can be used to share IT resources cost-efficiently and securely in cloud environments, in which a single instance of software runs on a server and serves multiple tenants.

It’s been years since we first heard about it; it came out again riding the wave of cloud computing, so we can assume that multi-tenancy is a consolidated architecture and the benefits in terms of maintainability and costs are well known.

APIs are the backbone of a distributed cloud architecture, so building a multi-tenant API is the natural aftermath in this scenario.

In this article I will show you how to build a simple multi-tenant RESTful API using Java in a quick and easy way, dramatically reducing the configuration code amount required by the most frequently adopted solutions.

A multi-tenancy implementation may be cumbersome

The first impression, looking for multi-tenancy implementation strategies, is that there’s no specific standard and no well defined best practices.

One of the most common solution is relying on Hibernate to implement multi-tenancy behaviour at DataSource level. Hibernate requires two interfaces to be implemented: CurrentTenantIdentifierResolver to resolve what the application considers the current tenant identifier and MultiTenantConnectionProvider to obtain Connections in a tenant specific manner. This could be tricky and not so trivial, possibly leading to boilerplate code rising.

And finally, last but not least, this solution is only suitable for a JPA implementation, since Hibernate is directly used as tenant resolver and connection router.

Furthermore, the Spring Framework is often used in conjunction with Hibernate to build a multi-tenant API or application. One of the most useful aspects of the core Spring architecture is the availability of scopes: in a multi-tenant scenario, sooner or later you’ll surely miss a tenant scope, which is not available out-of-the-box. Creating a custom scope in Spring is not trivial, it requires a deep knowledge of the Spring architecture and a lot of code to implement and register the new scope.

You can check the Spring+Hibernate version of the example shown in the second part of this article here: https://github.com/fparoni/spring-boot-hibernate-multitenant-jaxrs-api.

The easy way

Now let’s streamline the process, in order to highly simplify the multi-tenancy setup and to cut down the required configuration effort, getting rid of all the boilerplate code.

We will use the Holon platform to achieve this goal, relying on the platform’s native multi-tenant support.
I’ll show you a working example of a simple RESTful API, using the following Holon platform’s components and configuration facilities:

  1.  The TenantResolver interface to provide the current tenant identifier
  2. A tenant Spring scope provided out-of-the-box, automatically configurated and registered when using Spring Boot
  3.  The Holon Platform JDBC datastore module to use JDBC for multi-tenant data access
  4.  The Holon Platform JAX-RS module to easily create a RESTful API using Java

The complete example is available on GitHub: https://github.com/holon-platform/spring-boot-jaxrs-multitenant-api-example.

Our RESTful API implements the CRUD operations to manage a simple Product, providing methods to create, update, delete and query the product data.

The multi-tenancy architecture of this example is organized per schema: each tenant is bound to a separate database schema and will access data through a different Java DataSource.

Each time an API request is performed we need to know the caller tenant identifier, to correctly route the persistence operations to the right database schema.

The three most common ways to provide the tenant identifier are:

  1. Providing the tenant identifier as a URL part, for example using a query parameter
  2. Using a custom HTTP request header
  3. Using JWTs to provide the tenant identifier as a JSON token claim

We will use the second option, through a custom HTTP header called ‘X-TENANT-ID’.

An example to use JWT to provide the tenant identifier is available in the jwt branch of the GitHub example repository.

Besides the Holon platform components listed above, for our example we will use:

  1. Spring Boot for application auto-configuration and to create a runnable jar with an embedded servlet container
  2. The H2 in-memory database for data persistence
  3. JSON as data exchange format

The Holon Datastore API will be used to access data in a technology-indipendent way (using JDBC in this example is only a matter of configuration) and the Holon Property model to define the product data model. Check to Holon reference documentation for detailed information.

The result will be a fully working example made of just three Java classes: now let’s build our example step by step.

Step 1: Project configuration

First of all, we will use Maven for project configuration and dependency management.

We’ll use the Holon Platform BOM (Bill of Materials) to easily obtain the dependencies we need.

 

<properties>
  <holon.platform.version>5.0.6</holon.platform.version>
</properties>

<dependencyManagement>
		<dependencies>
			<!-- Holon Platform BOM -->
			<dependency>
				<groupId>com.holon-platform</groupId>
				<artifactId>bom</artifactId>
				<version>${holon.platform.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
</dependencyManagement>

The Holon JDBC Datastore Spring Boot starter is used to automatically configure the data access layer:

<!-- Holon JDBC Datastore with HikariCP starter -->
<dependency>
	<groupId>com.holon-platform.jdbc</groupId>
	<artifactId>holon-starter-jdbc-datastore-hikaricp</artifactId>
</dependency>

Likewise we declare the Holon JAX-RS Spring Boot starter dependency to use and auto-configure Jersey as JAX-RS implementation.

<!-- Holon JAX-RS using Jersey -->
<dependency>
	<groupId>com.holon-platform.jaxrs</groupId>
	<artifactId>holon-starter-jersey</artifactId>
</dependency>

At last, the H2 database dependency:

<!-- H2 database -->
<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<version>1.4.196</version>
</dependency>

To configure our Spring application we will use an application.yml configuration file.

We simulate that two tenants are available, the first bound to a schema named tenant1 and the second associated to a schema named tenant2. So we need two DataSource instances, one for each schema.

Auto-configure more than one DataSource with the default Spring Boot DataSource auto-configuration is not possible without writing additional code. For this reason, we’ll rely on the Holon platform DataSource auto-configuration facilities to define the two tenant DataSources, through a holon.datasource configuration property structure like this:

holon:
  datasource:
        tenant1:
      		url: "jdbc:h2:mem:tenant1"
      		username: "sa"
    	tenant2:
      		url: "jdbc:h2:mem:tenant2"
      		username: "sa" 

By default, for each auto-configured DataSource, a Holon JDBC Datastore is created too. Each DataSource/Datastore pair will be available as a Spring bean and qualified with the name specified through the holon.datasource.* properties. For example, the DataSource bound to the first tenant can be injected in a Spring component using the @Qualifier(“tenant1”) annotation.

To be up and running at startup we will use a couple of .sql file to create a sample table named ‘product’ and one row for each tenant.

Step 2: Writing the code

As I said before we need just 3 Java classes.

1. Application definition and configuration

First of all, we create the Application class, which acts as the Spring Boot application entry-point and as the main Spring configuration class:

@SpringBootApplication
public class Application {

	@Bean
	@RequestScope
	public TenantResolver tenantResolver(HttpServletRequest request) {
		return () -> Optional.ofNullable(request.getHeader("X-TENANT-ID"));
	}

	@Bean
	@ScopeTenant
	public Datastore datastore(BeanFactory beanFactory, TenantResolver tenantResolver) {
		// get current tenant id
		String tenantId = tenantResolver.getTenantId()
				.orElseThrow(() -> new IllegalStateException("No tenant id available"));
		// get Datastore using tenantId qualifier
		return BeanFactoryAnnotationUtils.qualifiedBeanOfType(beanFactory, Datastore.class, tenantId);
	}

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

We have configured two Spring beans:

  • A TenantResolver implemetation to provide the current tenant identifier and to enable the Holon platform tenant scope. In this example, we want to use a custom HTTP header named “X-TENANT-ID” to obtain the tenant identifier from the caller. We need the current HttpServletRequest to obtain the header value, so we declare the TenantResolver as request-scoped and rely on Spring to obtain a reference to the current request.
  • A tenant-scoped Datastore is correctly retrieved by Spring through the tenant identifier which acts as bean qualifier.
    The two Datastore instances have already been configured by the Holon Platform Spring Boot autoconfiguration facilities, using the two DataSources declared through the holon.datasource.* configuration properties. The current tenant-related Datastore is retrieved using the bean qualifier which matches with the current tenant id, provided by the TenantResolver interface.

2. The data model

The Product interface represents our data model:

/**
 * Product model
 */
public interface Product {

	static final PathProperty<Long> ID = PathProperty.create("id", Long.class);

	static final PathProperty<String> SKU = PathProperty.create("sku", String.class);

	static final PathProperty<String> DESCRIPTION = PathProperty.create("description", String.class);

	static final PathProperty<String> CATEGORY = PathProperty.create("category", String.class);

	static final PathProperty<Double> UNIT_PRICE = PathProperty.create("price", Double.class)
			// not negative value validator
			.validator(Validator.notNegative());

	// Product property set
	static final PropertySet<?> PRODUCT = PropertySet.of(ID, SKU, DESCRIPTION, CATEGORY, UNIT_PRICE);

	// "products" table DataTarget
	static final DataTarget<String> TARGET = DataTarget.named("products");

}

The Holon platform Property model is used to define the product data model. Deepening this topic is out of the scope of this article, check the official Holon documentation for further details.

3. The API endpoint

The ProductEndpoint class represents the JAX-RS resource that will provide the API CRUD, using JSON as data exchange format. The class is annotated with @Component to make it available as a Spring component and we rely on the Holon platform Jersey auto-configuration facilities to automatically register the endpoint in the JAX-RS application:

@Component
@Path("/api")
public class ProductEndpoint {

	@Autowired
	private Datastore datastore;

	/*
	 * Get a list of products PropertyBox in JSON.
	 */
	@GET
	@Path("/products")
	@Produces(MediaType.APPLICATION_JSON)
	public List<PropertyBox> getProducts() {
		return datastore.query().target(TARGET).list(PRODUCT);
	}

	/*
	 * Get a product PropertyBox in JSON.
	 */
	@GET
	@Path("/products/{id}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getProduct(@PathParam("id") Long id) {
		return datastore.query().target(TARGET).filter(ID.eq(id)).findOne(PRODUCT).map(p -> Response.ok(p).build())
				.orElse(Response.status(Status.NOT_FOUND).build());
	}

	/*
	 * Create a product. The @PropertySetRef must be used to declare the request
	 * PropertyBox property set.
	 */
	@POST
	@Path("/products")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response addProduct(@PropertySetRef(Product.class) PropertyBox product) {
		// set id
		long nextId = datastore.query().target(TARGET).findOne(ID.max()).orElse(0L) + 1;
		product.setValue(ID, nextId);
		// save
		datastore.save(TARGET, product);
		return Response.created(URI.create("/api/products/" + nextId)).build();
	}

	/*
	 * Update a product. The @PropertySetRef must be used to declare the request
	 * PropertyBox property set.
	 */
	@PUT
	@Path("/products/{id}")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response updateProduct(@PropertySetRef(Product.class) PropertyBox product) {
		return datastore.query().target(TARGET).filter(ID.eq(product.getValue(ID))).findOne(PRODUCT).map(p -> {
			datastore.save(TARGET, product);
			return Response.noContent().build();
		}).orElse(Response.status(Status.NOT_FOUND).build());
	}

	/*
	 * Delete a product by id.
	 */
	@DELETE
	@Path("/products/{id}")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response deleteProduct(@PathParam("id") Long id) {
		datastore.bulkDelete(TARGET).filter(ID.eq(id)).execute();
		return Response.noContent().build();
	}

}

Note that we injected the Datastore instance to perform data access operations: thanks to tenant scope with which the Datastore bean is declared, we’ll obtain the right Datastore instance according to the current tenant identifier.

Run the application

The spring-boot-maven-plugin will create a runnable jar to have our application up and running using our JRE. We can run application using this maven command:

mvn spring-boot:run

Application is running under Tomcat, listening to port 8080. We can test our endpoint using Postman application and making GET or POST request to the path we’ve defined before. Let’s test the /products operation using both tenants. Setting the X-TENANT-ID header to tenant1 will produce the following result:

Using tenant2 as the X-TENANT-ID header value, the result is:

Summary

We’ve seen how to setup a multi-tenant API quickly, highly reducing the configuration effort and boilerplate code that is often required for this kind of task.
The multi-tenant architecture that we’ve used in our simple API implementation can of course be leveraged for other, more complex, services or applications.
The source code of the application can be found at https://github.com/holon-platform/spring-boot-jaxrs-multitenant-api-example.

We use cookies to ensure that we give you the best experience on our website.