Introduction
Coupling and Cohesion are two classic concepts in software engineering. In this article I explore ways to apply this concepts at multiple levels of any project.
Unit
First of all, is important to define what a Unit is in the context of this article:
- a unit is an element that compose a complex structure.
- a unit can be classified as:
- methods
- classes
- packages
- services
Coupling
what is?
- coupling is the degree of interdependency between units.
- coupling is the degree of how a unit
uses
other units directly.
how this affects our projects?
a highly coupled project can lead to problems like:
- when a unit needs to be modified you
must
, as anobligation
, to change the other units. - code that is hard to change and optimize.
how can I deal with highly coupled units?
there will always be a coupling degree in any project, the objective is to reduce that number.
in methods
in the example below registry
methods are deeply coupled to the register_vehicle
method.
class Application:
def register_vehicle(self, brand: string):
registry = VehicleRegistry()
vehicle_id = registry.generate_vehicle_id(12)
license_plate = registry.generate_vehicle_license(vehicle_id)
catalogue_price = registry.calculate_price(license_plate, brand, vehicle_id)
...
to reduce coupling I moved all that business logic inside the registry service
.
class Application:
def register_vehicle(self, brand: string):
vehicle_repository = VehicleRepository()
registry_service = VehicleRegistryService(vehicle_repository)
vehicle = registry_service.register_vehicle(brand)
...
in classes
the class CourierController
has a concrete class Whatsapp
as a dependency.
export default class CourierController {
constructor (private readonly courier: Whatsapp) {
}
}
to reduce coupling I created an interface
or abstract
class to be used by CourierController
.
export default class CourierController {
constructor (private readonly courier: Courier) {
}
}
in packages
many MVC frameworks use a coupled package structure. the controller package use views, and the views package use models.
to reduce coupling I applied vertical slicing
and structured the project based on business entities.
in services
when a project grows is common to have somethings like the image below, services are consumed by other services and this can produce performance issues in the architecture.
to reduce coupling between services, a queue
can be used, this allows to process expensive operation asynchronously and decouple the architecture.
Cohesion
what is?
- cohesion is the degree of relation of the elements inside a unit.
- cohesion is the degree is which every element inside a unit are directed towards perform a single task.
how this affects our projects?
a poorly cohesive project can lead to problems like:
- having many responsibilities for the same unit.
- code that is hard to test and maintain.
how can I deal with poorly cohesive units?
in methods
in the code below the internal elements of register_vehicle
have multiple responsibilities.
class Application:
def register_vehicle(self, brand: string):
# First responsability
registry = VehicleRegistry()
vehicle_id = registry.generate_vehicle_id(12)
license_plate = registry.generate_vehicle_license(vehicle_id)
# Second responsability
catalogue_price = 0
if brand == "Tesla Model 3":
catalogue_price = 60000
elif brand == "Volkswagen ID3":
catalogue_price = 35000
elif brand == "BMW 5":
catalogue_price = 45000
tax_percentage = 0.05
if brand == "Tesla Model 3" or brand == "Volkswagen ID3":
tax_percentage = 0.02
payable_tax = tax_percentage * catalogue_price
# Third responsability
print("Registration complete. Vehicle information:")
print(f"Brand: {brand}")
...
to improve cohesion I moved all that business logic inside the registry service
. Now the elements inside the register_vehicle
method are towards the same goal.
class Application:
def register_vehicle(self, brand: string):
vehicle_repository = VehicleRepository()
registry_service = VehicleRegistryService(vehicle_repository)
vehicle = registry_service.register_vehicle(brand)
vehicle.print()
in classes
in the example below the class Utils
have multiple responsibilities. This class will grow without any business role or good practice and will became a god class
to address this and improve cohesion, I relocated each of those methods to their respective classes, aligning them with their corresponding responsibilities.
in packages
in the example below a basic hexagonal architecture structure is shown. However, the elements within each package lack cohesion as they serve different purposes.
to solve this, screaming architecture can be applied.
in services
in the example below two services Sales and Invoicing
are shown. Both services have multiple responsibilities apart from its main ones, such as Auth, Email sending, AWS access, etc. This indicates a lack of cohesion within the architecture.
to solve this I grouped the admin services and the external services into their proper units and then I referenced those units from the Sales and Invoicing services.
Top comments (0)