Disclaimer
The tool was born as a
way to overcome the drawbacks of designing with little blood. I can hardly imagine a situation where the use of the tool could have been dictated by other reasons.
Prehistory
It happened to me one day to connect to the development of middle-aged web applications. The code was pretty tangled in places, kept traces of the activities of several developers of different qualifications, there were no actual workable tests. One word is legacy.
The application was implemented according to the classical three-layer scheme:
- Persistence: Hibernate.
- Services: Spring.
- Endpoints: Spring MVC: JSP, RESTful.
And at all levels, storage level entities (hereinafter referred to as entities) were used. Because of this, changing the names of the properties of the entities made it necessary to edit the code on the client or in the JSP, and adding something to the JSON that was given to the controllers or to the JSP, if this was something that was not in essence, was extremely inconvenient and risky from for lack of tests.
How to drag to the level of representation of values that are not in the essence?
I have considered several ways.
Listeners and Interceptors
Listeners and
Interceptors allow you to add additional data to the entities. In some cases, their use is justified, but I prefer not to litter the entities of the storage level with structures and data that have nothing to do with the storage level.
')
Mapping
You can learn from each class that requires expansion, and fill it with the necessary data already at the service level. This is the conceptually correct way: the storage layer is clean, the controller layer does not require modification. However, performance problems arise,
lazy loading is no longer lazy, because The mapper does not know which of the fields the controller needs, and is forced to shift everything. Controlling the mapper from the controller is theoretically possible, but this is impossible without modifying the controller code.
Dynamic proxy
A little bit of magic:
package ru.bdm.reflection;
What is under the hood?
Classes of interfaces are dynamically created for added properties:
public interface FirstHolder{ Object getFirst(); } public interface SecondHolder{ Object getSecond(); }
A proxy class is dynamically created that inherits
AnyType
and implements
FirstHolder
and
SecondHolder
.
The methods defined in the
AnyType
proxy are redirected to
src
, the methods defined in
FirstHolder
and
SecondHolder
are redirected to the
PropertyExtractor
, which contains the logic to calculate the additional properties.
Thus, we got the opportunity to expand the view, without changing the controller code and not clogging the storage layer entities with extraneous structures and data, without getting a drop in performance due to problems with lazy loading.
The charge for this was not very high: access to properties through a proxy is about 150 times slower than direct. It is worth considering when using the tool.
The load of our application was only a few requests per second, for each request, a maximum of 50 entities were read (page size), so that the share of losses in the proxy could be neglected.
You can download the code from
Google Drive .