3 simple optimisations for AngularJS + Java architectures

base-arch

Nowadays, web app architectures are including an AngularJS layer for user interface and a Spring Boot for REST services. As in the popular JHipster, which is selecting this kind of architecture.

In this post, taking above image architecture as reference, a performance optimization based in 3 simple actions is analysed.

These tests have been performed on a custom lab configured in my own developing laptop by using Docker Compose.

We are starting with a findAll method which is returning all the rows stored on a big database table.

  • Time: 47,47 seconds
  • Size: 21,6 MB

1. Adding database indexes

It happens that JPA abstraction hides that real Java objects are performing SQL sentences on a real database. And this database always need help to improve response times.

Using indexes in JPA annotations

@Entity
@Table(name = "element")
public class ElementEntity {

	@ManyToOne(optional = false)
	@JoinColumn(name = "category", foreignKey = @ForeignKey(name = "FK_CATEGORY_ELEMENT"))
    @Indexed
	private CategoryEntity category;

}

Using indexes in database

CREATE INDEX element_category ON ELEMENT(CATEGORY);

After including indexes, we can see how time is reduced.

  • Time: 40,88 seconds
  • Size: 21,6 MB

2. Using cache in service layer

Many web apps services are accessed mainly in read mode. For these scenarios, using Spring Cache with a third party product like Hazelcast provides faster results, as database layer is not used while cache is active.

Using Spring Cache

@Service
public class ElementServiceImpl {
	
    @Cacheable("elementService.findAll")
    @Transactional
    public List<ElementDto> findAll() {
    }

    @CacheEvict(value = { "elementService.findAll" }, allEntries = true)
    @Transactional
    public void delete(Long id) {
    }

    @CacheEvict(value = { "elementService.findAll" }, allEntries = true)
    @Transactional
    public ElementEntity save(ElementDto elementUpdated) {
    }

}

After this second optimization, it can be seen that time is also lower than before.

  • Time: 13,84 seconds
  • Size: 21,6 MB

3. Compressing JSON responses

Anyway, we can do it better. This kind of architectures are sending big JSON data objects to AngularJS layer, as web interfaces are complex and they need to provide different options to user. In our sample, 21,6 MB are sent to client.

Including an Apache HTTPd directive, we can compress JSON data before sending it.

Declaring compression for JSON responses

AddOutputFilterByType DEFLATE application/json
DeflateCompressionLevel 7
DeflateMemLevel 8
DeflateWindowSize 10

As it can be seen, time is now lower again.

  • Time: 6,49 seconds
  • Size: 4,5 MB

Final words

In our tests, we have optimized a REST service also in time as in network bandwidth.

  • From 47,47 seconds to 6,49 seconds
  • From 21,6 MB to 4,5 MB

We have used just only three simple techniques:

  • Indexing database and JPA layer
  • Caching service layer
  • Compressing web layer

Any other optimization techniques can be used, but don’t forget to use ever the simple ones.

Why choosing (public) open source is a so keen choice

I’m spending some days extending an aged OpenCMS project (so aged as OpenCMS 7.0) to include some new features. It’s not a nice task but someone has to perform it.

The facts

I’m trying not to inspect all this crazy code, but sometimes I simply can’t apart my eyes from it. Let me share with you this little piece of art, worthy of appearing in The Daily WTF or so. I want to specify that this lines are written (supposedly) in Java.

String orden = "";
if ((request.getParameter("orden") != null) && (request.getParameter("orden") != "")) {
	orden = request.getParameter("orden");
	if (orden.compareTo("") != 0) {
		logger.info("entrada1 con " + this.controlUsuario.listaUsuariosNombreApellidosUnidad(
		    nombre, apellidos, unidad, extension, fijo, tipo, empresa, orden, inactivos, 
		    idGrupo).size());
		request.setAttribute("resultados", this.controlUsuario.listaUsuariosNombreApellidosUnidad(
		    nombre, apellidos, unidad, extension, fijo, tipo, empresa, orden, inactivos, idGrupo));
		myModel.put("resultados", this.controlUsuario.listaUsuariosNombreApellidosUnidad(
		    nombre, apellidos, unidad, extension, fijo, tipo, empresa, orden, inactivos, idGrupo));
	} else {
		logger.info("entrada2 con " + this.controlUsuario.listaUsuariosNombreApellidosUnidad(
		    nombre, apellidos, unidad, extension, fijo, tipo, empresa, "apellidos", inactivos, 
		    idGrupo).size());
		request.setAttribute("resultados", this.controlUsuario.listaUsuariosNombreApellidosUnidad(
		    nombre, apellidos, unidad, extension, fijo, tipo, empresa, "apellidos", inactivos, 
		    idGrupo));
		myModel.put("resultados", this.controlUsuario.listaUsuariosNombreApellidosUnidad(nombre, 
		    apellidos, unidad, extension, fijo, tipo, empresa, "apellidos", inactivos, idGrupo));
	}
} else {
	logger.info("entrada3 con " + this.controlUsuario.listaUsuariosNombreApellidosUnidad(nombre, 
	    apellidos, unidad, extension, fijo, tipo, empresa, "apellidos", inactivos, idGrupo).size());
	request.setAttribute("resultados", this.controlUsuario.listaUsuariosNombreApellidosUnidad(nombre,
	    apellidos, unidad, extension, fijo, tipo, empresa, "apellidos", inactivos, idGrupo));
	myModel.put("resultados", this.controlUsuario.listaUsuariosNombreApellidosUnidad(nombre, apellidos,
		unidad, extension, fijo, tipo, empresa, "apellidos", inactivos, idGrupo));
}

A client payed for this work as if it were developed by a Senior Java Developer supervised by a Senior Java Analyst.

The analysis

For those of you not used to read Java code, I’m going to point out some curiosities:

  • Strings are compared by using equals String type function in Java, so request.getParameter("orden") != "" will not work as expected in any case
  • As this programmer (or maybe other) realised that this condition was not working, he decided to use that other fancy expression orden.compareTo("") != 0, which works but is not so usual. Surprisingly, he left the incorrect comparison expression in the previous line
  • These are just only minor considerations, I’m saving the best for last. Despite objects are not known by this developer (10 arguments functions, really?), that controlUsuario.listaUsuariosNombreApellidos(...) included three times in every case appear to be dangerous. Let me check implementation by using a simple CMD+Click… Bingo!
    • Every controlUsuario.* is a DAO invocation, which is performing a full table scan query on database
    • 3x time for every search that can be simply avoided by using your fingers (and your mind) instead of CMD+C

The thinking

Open Source and public source code is always a better choice than closed code.

If that sample we were talking above were published in some kind of tool like GitHub, any developer had discovered this (big) defects and (even) he could collaborate to improve it. However, as it was a closed project, it has remained hidden during years, consuming database resources without limits and wasting thousand of hours from users in front of the screen.

After that, are you yet trusting in those closed systems? Not me.

Note Obviously I’m referring as “close” to that extension developed for OpenCMS, not to OpenCMS itself which is a clean sample of Open Source product