POSTED ON 13 FEB 2020
READING TIME: 6 MINUTES
Modern web applications are complex; there are a multitude of ways of building them and a seemingly endless set of technology choices. Starting a project with an empty directory can be daunting and it can take a lot of effort to get beyond a canonical “Hello, World!” app. JHipster, a popular application generator, best-of-breed technology choices with industry best practice design to allow developers get on with developing their application logic and user interface without getting tied up in the plumbing unless they really want to.
We’ve certainly found JHipster to be very useful for certain types of projects but found that we needed to add multi-tenancy support to the JHipster applications on more than one occasion. Unfortunately, JHipster doesn’t provide multi-tenancy support as an option but it has an extension mechanism allowing developers to create Blueprints that extend JHipster functionality.
With that in mind, and given that multi-tenancy is quite a common requirement for us, we took the opportunity to learn more about JHipster Blueprints and developed our own for creating multi-tenant applications.
Our Blueprint structures your JHipster application to support multi-tenancy. Once you run it, your generated JHipster application will have the ability to provision and manage tenants and users, with all tenant aware data automatically separated. The Blueprint is very easy to implement and extend, providing the core values of multi-tenancy, to a known coding standard.
Out of the box, you will get the following:
There are three common approaches to multi-tenancy:
After some investigation, we found the most suitable option was to implement a custom discriminator using Spring, Hibernate, and AspectJ. Here is a breakdown of the key operations that the Blueprint performs to provide multi-tenant functionality to your JHipster application.
@Entity
@Table(name = "company")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT\_READ\_WRITE)
public class Company implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
…..
}
@Entity
@Table(name = "jhi\_user")
@FilterDef(name = "COMPANY\_FILTER", parameters = {@ParamDef(name = "companyId", type = "long")})
@Filter(name = "COMPANY\_FILTER", condition = "company\_id = :companyId")
public class User extends AbstractAuditingEntity implements Serializable {
…..
@ManyToOne
private Company company;
…..
}
@Before("execution(\* com.mycompany.myapp.service.UserService.\*(..))")
public void beforeExecution() throws Throwable {
….
User user = userRepository.findOneByLogin(login.get()).get();
if (user.getCompany() != null) {
Filter filter = entityManager.unwrap(Session.class).enableFilter("COMPANY\_FILTER");
filter.setParameter(fieldName, user.getCompany().getId());
}
}
To use the Blueprint, follow these steps.
$ npm install -g generator-jhipster-multitenancy
$ jhipster --blueprints multitenancy
What is the alias given to tenants in your application?
$ jhipster entity Survey
Do you want to make Survey tenant aware? (Y/n)
Development of this JHipster Blueprint presented some challenges. Broadly, these can be categorised as:
One of the great benefits of JHipster is that it provides a wide range of options when generating your application. Providing support for every combination of the configuration options can make things tricky when developing a Blueprint. For example, entities can have no pagination, pagination, or pagination with infinite scrolling which complicates how a Blueprint can safely modify the default JHipster frontend templates.
JHipster development is moving at a rapid pace. With new versions released every couple of weeks maintaining Blueprint compatibility continues to be a challenge. To address this our strategy is to minimise the number of JHipster files that are modified by the Blueprint. This lessens the chances that new or changed JHipster code will be overwritten by the Blueprint.
Partial updates of generated JHipster files, both frontend and backend, can be done during the writing phase of JHipster Blueprints. This process is achieved by detecting a specific piece of code in a file using regular expressions, and inserting custom code either instead of, or alongside regex matches. Establishing the correct regex pattern to use to detect the targeted code can be a time consuming process and frustrating. This is mainly due to the different stages of formatting that JHipster carries out during the code generation process.
The JHipster development team follows certain coding policies to ensure best practices on all the code they deliver. We value this and make every effort to ensure that the multi-tenant Blueprint adheres to the high standards of the JHipster team. A big challenge throughout Blueprint development is ensuring that the generated code remained true to this objective.
All this takes work and often we struggle to make the time to keep up to date with the pace of JHipster development. If you’re a Java, Angular, React, or Vue.js developer and you’d like to help, please get in touch!
Check out the code for the Blueprint on GitHub, or if you just want to see what the Blueprint gives you, check out our sample Angular and React apps.