During my career, I worked with different legacy projects, each of which suffered from various flaws.
Of course, often the main problem was the low quality of the software (lack of modular tests, refusing to use the principles of clean code ...), but there were also difficulties, which originated from architectural decisions taken at the beginning of the project or even during the inception of the corporate system. In my opinion, this class of problems is the cause of the greatest pain for many projects.
In essence, improving the code is quite simple, especially now that the software craftsmanship movement promotes good practice in teams. However, changing the core parts of the systems, the restrictions imposed at the very beginning of their life cycle, is an extremely difficult task.
I’ll tell you about several types of architectural solutions I have encountered that can be a real burden for teams involved in supporting such systems.
This is perhaps one of the most common problems I've seen. When multiple applications need to share data, why don't we just give them shared access to a common database? After all, in software development, duplication is considered evil, right? Well, this is not always true, and especially when it comes to the database. Venkat Subramaniam expressed this idea in the following way: “The database is like a toothbrush: never let anyone use it.” What's so bad about database sharing? In fact, quite a lot ...
The first thing that comes to mind is the coupling of the data model. Imagine that two applications, A and B, are processing data on cars. Appendix A is used by the team responsible for repairs, and therefore they need to store a lot of technical information: about mechanics, breakdowns, history of vehicle interventions ... Appendix B is used by the technical team to set tasks, so that it only needs basic information about the car, sufficient for his identification. In this case, using the same data structure for both programs does not make sense: they use different data, so each must use its own data structure. It is easier to make it so that the car is easy to identify, so there is no need in the general directory.
The second problem is also due to this data model link. Imagine that application B wants to rename the car's identifier - to give it a more logical name from the point of view of its subject area. In this case, application A will also have to be updated - the column name has changed ... As a result, in order not to disturb the application team A, developers B will duplicate information in another column, since they cannot change an already existing column ... Of course, developers A will say that they plan to change the name of the column for the future so as not to store the same information in two columns, but we all know: most likely, this will never be done ...
Everything becomes even more disgusting when applications do not just read data from one source, but also change them! Who is the data owner in this case? Who to trust? How can data integrity be guaranteed? It is difficult even in the case when the same data is changed by different parts of the same application, but when several different applications do it, the problem becomes much more serious ...
The last case I have seen: two applications sharing the same data structure for storing information about two business objects are relatively similar, but at the same time different enough to seriously complicate the understanding of which application which data belongs to. Both applications used the same table to model executions for the financial market, but with different levels of aggregation. Nothing indicated that the table stores two types of data, so we had to look at another table (belonging to the second application) to determine the rows generated by each program ... Each new developer who had to work with this table, inevitably occurred on the same rake as all his predecessors, and used incorrect (vulnerable) data, with all the resulting risks for the company.
Not every company can develop software that meets all the needs of its business. In fact, in many cases this would be the invention of the bicycle, since the needs of many companies are the same, and it is easy to find software on the market that already has the necessary functionality.
In general, it is often cheaper to buy a product than to create it. But, of course, the product you just bought will not immediately work smoothly with the software that you already use, so you need to create a bridge between two (in most cases - proprietary) applications. You will surely develop your own tools for specific parts of your business, and as long as this expensive software that you have already acquired has a suitable model, you just have to use its database and add your data to the tables in this database ...
It takes several years, a dozen developers or teams perform these actions again and again, and then you find yourself at an impasse: you simply cannot use other software if the developer of the software you purchased closes it, or stops supporting the product, or some new software turns out to be best suited to your needs. In some cases, you may even have technical dependencies on external software. If the author of the software solution you use wants you to use the version of the language / framework / something he needs, this means that the architecture of your system does not belong to you. If you want to sell a new version to provide a function that you absolutely definitely need, but this version pulls a change in technical requirements, you will be forced to update your technological stack to match other people's recommendations. I know what it is, and you would not want such forced migration to be a frequent affair ...
I worked on a project where the developers of the product we used did not want to add new features to all of their customers, because it was too difficult for them to maintain competitive changes and several current versions (each client had its own version with functions necessary only for him). So they decided to sell us the SDK so that we could implement our own functionality. Naturally, they did not provide enough documentation on how to do this, and, moreover, we had to use their business entities, which we had to decompile in order to understand their structure - after all, we did not have any source codes or documentation ... Implementation It took a few days for the simplest functionality, and it was almost impossible to test it, because everything was too complicated, but it also required knowledge of the scripting language that no one in the team with an already complicated technological stack knew ...
Remember the early 2000s: how nice it was to use Enterprise Java Beans (EJB) to handle remote calls between applications in your information system. At the time, this might have seemed like a good idea. Sharing your code with other teams to avoid duplication also looks good. Yes, all the teams were forced to roll out their applications at the same time to make sure that binary dependencies did not break, but it was a fun evening - eating pizza with colleagues during the 2-hour wait for the application to complete, wasn't it?
Well, actually it wasn't so much fun. And the inability to refactor a separate class in your own code — simply because someone else in the company liked your code and they decided to use it in their untested application — this is still a pleasure.
As soon as you realize what a mess these early decisions are causing, the effort required to separate your application from the rest of the world turns out to be enormous. You can see that it will literally take years to slice your project into various components so that other applications can no longer use the core of your subject area, your client, or the caching mechanism; to remove all references to external classes that lead to strong engagement with other projects; to replace all EJB calls with REST APIs ... However, for all these efforts, every employee associated with the project will be rewarded with interest: development and testing will be simplified, the delivery process will be accelerated, as from now on there is no longer any need to synchronize your work with someone else ; concepts in your own code will be better separated; management of dependencies will be simplified, problems with transitive dependencies will disappear when you import a cloud of dependencies of other applications into your classpath ... All these costly changes will simply save the team’s life, and they could be much simpler to implement, do you at the dawn of the project!
You most likely will not encounter this problem, but it can still happen, and if it does, this is the worst case scenario, because it combines several of the problems discussed earlier. In truth, I came across a similar one of the first projects of my professional career.
When I joined the project, I was told that the corporate system was completely rewritten and that the work on the project began only recently, 2 months ago. What was my surprise when I saw a complex web application with a full-fledged administration module, an already implemented complex business functionality and a developed framework for writing other modules. Soon I learned that most of this was not written by my team: in order not to start from scratch, it was decided to reuse the framework developed by another company of our group. The problem was that this framework was not isolated from the project for which it was designed. So our team just received an archive with all the source code of the project of another company, including their business logic, which had nothing to do with our own business. Worse, we also inherited from them the database schema and the data itself ...
For a newcomer to the team, it was not easy to understand which code belongs to the framework, which one - to our project, and which one - to the business of another company. The team wanted to clear this mess, but several attempts to make it ended up with serious regression errors due to dependencies between parts of the code (the language doesn’t turn around to call it modules, because there was only one module!), And, of course, there are no automated tests there It was. Moreover, we had to abandon the idea of ​​using another application server, since it was a specific code used by another company throughout the system, and this made such a migration too expensive for our small team.
At some point we wanted to add some nice features to the framework, but we were told that this was already done by another company. So we were asked to merge our current version with the current version of the code of another company ... The team managed to avoid this nightmare by simply transferring (cherry-pick) a part of commits related to the new function, but it was still too complicated and sophisticated compared to that we needed ...
We managed to finish this project, but its quality was a real pain. At least 40% of the code and the contents of the database were ballast unused, and the removal of this dead code did not become a priority. I hope the team finally had the opportunity to separate their own code - I don’t know, I left the team before it happened!
Putting some of your business logic in a rule management system is a common practice. This is useful, for example, if some of your business rules need to be updated frequently, and the delivery process of your monolithic application requires a long testing phase before the release candidate can be checked, and this makes it impossible to configure some of your “changeable »Rules. Although I prefer the approach when all the rules of the subject area are in the source code, I can understand that in some situations the rules management system can be useful.
However, I ran into an application where almost ALL business logic was in the rules management system, and sometimes rules were generated based on an Excel file! Moreover, the rules were not supposed to be changed very often, since the project was, by and large, a simple ETL package . The Java project on which it was based, consisted only of technical details about the batch processing framework and pure read / write from the source and target systems, without any connection to the subject area.
As a result, all the rules were written in a special language that no one really knew well on the team, which was difficult to write (our IDE did not support it) and which was almost impossible to debug and test. When a new rule or a change to an existing one was requested, most of the developers in the teams simply copied the existing rule, which resulted in completely identical files, except for one specific change (often the only change was the field to which the rule was applied).
If this already seems to be a problem, here is another thing: there was absolutely no clue about its purpose in any rule. The rules were called like Rule1, Rule2, and so on, for a total of more than 100! And basically, each rule consisted of checking and assigning hard coded values ​​without any terms of the subject area. Even the name of the project did not clarify the purpose of the entire ETL as a whole.
As Uncle Bob explained in his book “Clean Architecture”, when considering the architecture of your project, you should postpone some decisions until we really need to make a choice, without which we cannot continue to add value to our product (like choosing a database , eg). Other decisions must be made really early - do not wait until everything becomes bad. Fortunately, this kind of critical solutions are easy to identify, because they are what can be called “architecture with a touch”: when you think about such ideas, you don’t see anything good in them - only a problem that will come back sooner or later chase you. Unfortunately, when working with legacy projects, this kind of burden is often buried deep in the code, which makes its removal a very expensive task.
But we should not be afraid. Yes, cleaning up the clutter that has accumulated over the years and even decades is not an easy task, but as professional software developers we simply cannot allow such problems to continue to rot and kill the developers' motivation, the trust of customers and our ability to benefit them invested in our product.
Of course, each type of architectural burden described by me can be eliminated in various ways - there is no silver bullet to solve any of these problems. However, I am sure that any team can come up with something in order to finally get rid of such a burden. So let's face our problems together and start ordering things!
Source: https://habr.com/ru/post/427591/
All Articles