It really strikes me how, over the years, I’ve worked on applications with .NET and C#, with JavaScript using Angular and Node.js, and also with PHP when I was more focused on building Drupal modules—and how the same good practices can be applied across all those languages and their respective development environments.
Sometimes those practices concern work procedures or the way a project is structured; others involve applying design patterns and even anti-patterns to identify what isn’t being done well. In other cases, they’re simply common practices followed by most people using a particular technology (hence, for example, scaffolding projects).
A good software project should smell of good practices from beginning to end, from its architecture to the evolution of the features gradually added to it.
There’s nothing worse than an application, project, or system where it’s impossible to see clearly how those good practices have been implemented.
And they also have something in common: most of them are easy to implement.
Since I enjoy writing… I’m going to talk about them, especially the ones I’ve seen firsthand make the biggest difference to the quality of a software project.
A word of warning: in many cases, the benefits of these recommendations aren’t immediately obvious, not even in the short term. Their positive impact over the medium and long term is more subtle. You only learn it after making every possible mistake; then the light comes on and you say something like, “Now I get it.”
For me, implementing data repositories is one of the most useful practices in any software project.
The general principle behind implementing a data repository is this:
All access to the data an application uses (whether it comes from databases, data entities in the cloud, cached data, and so on) should go through a single point.
Depending on the technology and type of application being developed, that single point might be a module with its classes, a library, an API, a REST service, and so on. What’s important is that all those access paths are centralized and easy to locate in the project.
What benefits does this provide? Many, many benefits—some of which aren’t easy to see at first.
In short, centralizing data access lets us:
- Follow the SoC principle (separation of concerns), which is fundamental to agile development. This gives us a specific, well-defined part of the project dedicated to accessing the data it uses.
- Avoid duplicating code by repeating the same accesses to the same data throughout the solution. Instead of running something like
SELECT * FROM dbo.users WHERE userId = 20every time user information is needed, we can use something more general, such asdata.getUserById(20)(just a simple pseudocode example!). - Simplify the solution and reduce the number of lines of code. By encapsulating data access in specific methods, we don’t need to clutter or obscure the application’s business logic with data-access details (such as opening and closing a database connection), let alone when those accesses are more complex and require transactions or nested joins.
- Make simple profiling easier: by reviewing which data the repository object accesses and how, we can determine which indexes are needed in relational databases and implement performance tests that can run continuously.
- Decouple the solution from the way it accesses data. What if we change database engines tomorrow? What if, for scalability, we decide to distribute data across several databases, possibly using different technologies? Nothing is worse than clouding every part of the solution with data-access details. Since everything is encapsulated in the repository, only the repository needs to change. The rest of the application won’t even notice.
- Create specific unit and performance tests, since the repository implementation is decoupled.
- Easily create mocks. What happens when the “users” table has a hundred thousand records? Can the front end handle it? A data repository makes it easy to simulate different scenarios in anticipation of scalability problems and, above all, to cover those scenarios with tests.
- Make it easier for the person responsible for database development and data access to work in that specialized area, since all access is centralized.
- Ensure that migrations of the data repository don’t turn into a nightmare, because its access points aren’t scattered throughout the solution.
Some time ago, certain technologies used the term DAL (data access layer) for the same concept, though it was more closely tied to relational databases.
If you use an ORM that creates objects around data entities, should those objects travel through the business logic? I come across this question often, and my answer is no, because doing so creates an obvious dependency on the ORM technology being used. The objects passed through the business logic should be similar, but not tied to the ORM technology (they’re called data transfer objects, or DTOs).
At the end of the day, you need to use common sense (along with a little experience, and maybe some luck…): if we’re working on a project with a very clear lifecycle, and we know it will end and won’t evolve, we can take certain liberties. But if we’re dealing with a product or project that will grow and evolve in functionality over many years, then we do need to be very careful about these decisions and dependencies, and apply every good practice with even greater rigor.
Yes, I know: sometimes there isn’t enough time, the project has a very tight hours budget, and so on. Fair enough—but that’s all the more reason to apply these good practices from the start, because only then can the project move forward more productively and quickly.
All this is easier to implement than to describe. Still, a good software project that needs any mechanism for persisting data should access that data through an object, module, or library that implements this good practice.
Some useful links: