Donald Barre
Donald A. Barre
ORMs & Tight Coupling
Let's start with a simple data model with only two entities: an employee and a department. Every employee belongs to one and only one department but a department can have many employees. To implement this, we would have two classes: an Employee and a Department. We would also use an ORM to define an association between the two.
Let's suppose we are using Java and JPA. The EmployeeEntity class might resemble:
@Entity @Table(name = "employees") public class EmployeeEntity { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; @ManyToOne @JoinColumn(name = "department_id") // Sets foreign key column private DepartmentEntity department; // ... other properties }
The DepartmentEntity class might resemble:
@Entity @Table(name = "departments") public class DepartmentEntity { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; @OneToMany(mappedBy = "department") private List<EmployeeEntity> employees = new ArrayList<>(); // ... other properties }
When we do this, we are tightly coupling the two classes together. Now, instead of only two entities, imagine hundreds, and they are all interconnected via various associations. We end up with a massive web of interconnections.
This often leads to violations of the Law of Demeter such as the following:
String salary = employee.getDepartment().getCompany().getManagementTeam().getCeo().getSalary()
This is yet another example of tight coupling. For a great example of why violating the Law of Demeter is problematic, please read The Law of Demeter by Example.
💡
Another danger of the dot notation with ORMs is the likely possibility of database calls without the developer being aware of it.
So, what can we do instead of the above? First, we need to realize that our Data Model is not our Domain Model. ORMs are great, but they represent your Data Model only. It's how the data is being stored in a database. We should never allow database details to mingle with our business logic. To put it another way, the ORM should never find its way into our business code. If we ever need to change our the data is stored it should not impact the business code. And as was mentioned above in the callout, the ORM could make unexpected database queries. We can use Hexagonal (or Clean Architecture/Onion) to keep our business code and the ORM separate.
Secondly, we should take a page out of Domain-Driven Design (DDD). In DDD, the Root Entities, also known as Aggregates, are not directly associated to other Aggregates. Using our example, the employee class should never have a direct reference to the department class. Rather, it simply has the ID of the department.
public class Employee { private UUID id; private UUID departmentId; . . // business methods } public interface EmployeeRepository { void save(Employee employee); Optional<Employee> getById(UUID id); }
Note that the Employee class is NOT an ORM entity. It's a business object. And the ORM operations are hidden by the interface. Thus, the ORM never bleeds into the business code.
© Donald A. Barre. All rights reserved.