In software development, a monolithic object or class refers to a design where a single class performs a variety of functions that could logically be separated. These classes for example handle data access (like database queries), business logic (application-specific rules), and sometimes even presentation logic (UI handling). This approach often leads to a lack of modularity, making the code less maintainable, less scalable, and harder to understand.

A customer class that provides access to the customer table, the navigational method to perform a seek/search or add new entries as well as accessing the data is a good example of that bad practice, Unfortunately it is also a common practice to have so-called "heavy data object". Let's look into the major problem resulting from that software design and see how to avoid that by following some simple rules/guidelines.

Problems with Monolithic Design in Data Objects​

Violation of Single Responsibility Principle: In OOP, the Single Responsibility Principle (SRP) states that a class should have only one reason to change, meaning it should only have one job. When data objects mix data attributes (like `lastname`, `firstname`) with data access methods (`find()`, `update()`, `findAll()`), they handle multiple responsibilities – one responsibiility is representing data and the other managing data access. In reality it goes very often even further, over time these type classes develop even more responsibilities like addCustomer(), addLead() or addPotentialLead.

Difficulty in Maintenance and Scalability: As your application grows, so does the complexity of these classes. It becomes increasingly difficult to maintain and update them without introducing bugs, especially if changes in data representation require also adaption at the data access or logic level of methods and vice versa. Over time the classes are becoming a blackhole of code with not only 10 or 20 methods, now with respect to the inherited methods from base class we have seen classes with more than a hundred methods.

Poor Testability: Testing becomes more challenging with monolithic classes. You might want to test business logic without relying on the actual database. However, if your data access logic is tightly coupled with data representation, this separation becomes cumbersome.

Lack of Flexibility and Reusability: Suppose you want to switch your database from SQL to NoSQL. In a monolithic design, this change impacts the entire class, affecting both data access methods and data representation. Modular design allows you to change the data access layer independently of the data representation. So finally leaving business logic or UI intact.

Better Approach: Separation of Concerns​

The idea is to separate the responsibilities into different classes:

Data Transfer Objects (DTO): These objects should purely represent the data structure without any business logic. They should be simple structures with attributes like `lastname`, `firstname`, etc., and if required type specific validation. Use Xbase++ DataObjects() in case you just require a data container, In case you need to add validation use classes with set/get methods - avoid access/assign vars.

Data Access Objects (DAO): These are dedicated classes for data access. They contain methods like `find()`, `update()`, and `findAll()`. DAOs handle all interactions with the data source (like a database) and return DTOs. Do not divide the DAO into the categories of tables, have DAO for the specific domain of your data model such as Order, Invoice or ContactAddress.

Service/Business Logic Layer: This layer contains the business rules and logic. It uses DAOs to interact with data sources but keeps the business logic separate from data access logic. It binds the DTO with the UI or the outer world via RESTful interfaces.

Advantages of This Approach​

Clarity and Maintainability: Each class has a clear purpose. Changes in business logic or data access methods don’t spill over into unrelated areas.

Easier Testing: You can mock DAOs for testing business logic without needing an actual database, leading to more robust and faster tests. You can mock DTOs for testing the UI or business logic.

Flexibility and Scalability: Changing the data storage or representation impacts only the respective layers, not the entire application.

Reusability: DAOs and DTOs can be reused across different parts of the application without any changes.

Conclusion​

For professional software developers, understanding and applying these principles is crucial for building scalable and maintainable software. By adhering to the separation of concerns, you ensure that each part of your codebase does one thing and does it well, which is a hallmark of good software design.

When designing data objects, remember:

Keep Data Objects Simple: They should represent the structure of your data without being burdened with additional responsibilities. Think of them as blueprints for your data.

Isolate Data Access Logic: Create specific classes or modules for handling data access. This isolation makes your code more adaptable to changes in data storage or access methodology (like ISAM vs. SQL, or local vs. remote data).

Use Service/Business Logic Layer for Rules and Operations: Business rules should not be mingled with data handling or representation. This separation makes it easier to manage and evolve the business logic independently and finally allows an easy transition from desktop to web/mobile or a microservice (MSA) solution.

In summary, avoiding the creation of monolithic objects in favor of a more modular, responsible approach not only leads to cleaner and more manageable code, but also prepares your software for easier adaptation and growth in the future. The Xbase++ platform comes with all the features needed to design/implement classes, interfaces, DTO and DAO - so there is no need to re-implement an existing Xbase++ solution, instead follow the principles outlined above at least in any code you add and try to refactor your existing code gradually and step by step to apply the principles there.

We know that over time you will have a more manageable and easier-to-modify but also high-quality software solution, in other words your software production and maintenance costs will develop in the right direction.
  • Like
Reactions: Osvaldo Ramirez