We’ve all learned object oriented programming to keep our system being from monster. In object oriented programming, we always talk about Encapsulation, Polymorphism, Inheritance, IoC, DI and so on. But as a developer, we need to think about the point of object oriented programming.
Think about why we learn object oriented stuff? I think the answer is to keep our system maintainable. Then what is meaning of maintainable? Maintainable means we don’t need to code lots of crazy things to satisfy our ever changing business requirements.
What makes us to code all night to change such a little feature? I think one of biggest cause is lack of dependency management. How we create dependency between objects makes great impact on whole system architecture.
In other words we can see how we design our software as making decisions of how object will make relationship with other object. For example, ‘What code would be placed in this class’ or ‘What classes are going to be placed this package’, all these kinds of decisions are kind of software design.
Then is there any principle to put your code on which class or which packages? One simple but fundamental rule is focusing on changeability. If A and B should change together? Then place those in same packages. If not, do not.
Dependency

If A’s change makes B’s change, then we can say that B is depends on A. In other words, depdency is possibility to be change by other object’s change.
There are two basic kinds of dependency.
Association




class A { private B b; }
If A has association with B then it means A has permanent path for accessing B. This is conceptual explanation and how to implement association is by A having fields for B.
Dependency




class A {
public B method(B b) {
return new B();
}
}
If A has dependency with B then it means A has temporary relationship with B. In the case of A having B as parameter or return type in method, we can say A has dependency with B.
Real World Example
We’ve talked that designing software architecture is about consideration where to place classes, packages, codes. Codes which have high possibility to change together should be placed nearby, i.e. in the same class or package, so that the code cohesion can be increased. System which has high cohesion can be changed easily by developers. Because each part of the system takes in charge of single responsibility which remove possibility to break system as a result of changing other component.
For further explain how could we improve our system in terms of dependency, let’s take a real world example. I brought scenario in which customer order product on e-commerce service. And we are operating e-commerce system.
The code of this application can be found here:
https://github.com/zeroFruit/oop-dependency/tree/master/ecommerce-00
Service Flow
Basic flow of our service is as such:




- Customer orders item with options, for example customer ordered two “Fusion5 ProGlide Power razors” with “4 razor blade” and with color of “white”.
- If customer ordered successfully and payed, service takes commission fee and start shipping
Business Rules
And this e-commerce service has serveral business rules or restriction, we should follow these rules when designing our system:
- Our product list can be changed in real time, this can be happened because product suddenly out of stock or we may decided not to sell this product anymore.
- As a result if customer order on our service we should check whether ordered product name is matched with our service’s product name.
- In addition to product name, we should check ordered product’s option
- In our service, each product has minimum order amount, price.
- This is not ordinary but our service has closing hour (for system check hours 🙂 ), so we should check whether our service is opened.
Organize into class
We can see these kinds of rules as order validation logic in software point of view. We should check these kinds of things in our service:
- should compare order item name with product name.
- should compare order item option group name with product option group name.
- should compare order item option name with product option name
- should compare order item price with product price
- should check whether our service is available
- should check whether order item price is over product’s minimum order price
Based on business scenario diagram, we can map these rules into classes with its own methods.




With this diagram, we can find out what objects will be collaborated to work with validation and order and see which object will make relationship with others.
But before we dive into code to implement this design, there’s one more thing we need to decide: direction of relationship.
Direction of relationship
Direction of relationship in other words tells us what class will depend on what class. In the code this will be implemented by one class having other class as field variable or by having as method parameters.
For example, having other class as field variable, is called association. As we talked before association is permanent relationship.




With association, we can create path to navigate the other objects. For example, if Order
has association with OrderItem
and if we know Order
, we can find any OrderItem
through Order
. If these two objects need to collaborate to satisfy business rules and we need a way to find the other object. We can make association.
@Entity
@Table(name = "ORDERS")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ORDER_ID")
private Long id;
@Column(name = "USER_ID")
private Long userId;
// Order makes association with OrderItem
// We need a way to navigate OrderItem
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "ORDER_ID")
private List<OrderItem> orderItems;
public void place() {
validate();
ordered();
}
private void validate() {
...
for (OrderItem orderItem : orderItems) {
orderItem.validate();
}
}
}
Association is conceptual thing and there’s lots of way to implement this thing and this is one example. Order
has OrderItem
as a list. This two has strong relationship, whenever order is placed we need to get its information from order items.
As such we can decide direction of relationship.




The direction of arrow tells us which object is associate or dependent on other object. In the case of ‘Order’ and ‘Store’, arrow points to ‘Store’ with solid line. This means that ‘Order’ is associate with ‘Store’ and in the code Order
class will have Store
as field variable.
In the case of ‘OptionGroupSpecification’ and ‘OrderOptionGroup’ arrow points to ‘OrderOptionGroup’ with dotted line. This means that ‘OptionGroupSpecification’ is dependent on ‘OrderOptionGroup’ and in the code OptionGroupSpecification
will reference OrderOptionGroup
in the method as parameters or as local variable.
From the layered architecture view
Let’s think about these relationship from layered architecture.




All we’ve talked about objects and relationship goes into ‘domain’ layer. And such a word like ‘service’, ‘domain’ and ‘infrastructure’ is conceptual thing.
How we implement this is another topic. In java, these layer can be implemented through concept of packages.
So we can place our Order
, Store
classes into ‘domain’ package and OrderService
, StoreService
which use domain objects and make application API will be placed in ‘service’ package. Below diagram shows packages with its dependencies.




But there’s one big problem. You see? There’s cyclic dependency between ‘store’ and ‘order’ in domain layer.
Having cyclic dependency is a signal that architecture designed not that well and means that those packages are strongly coupled which cause seperation of responsibility hard and one component’s changes break other components, plus it is hard to test.
Problem
So where is the problem?




To figure out the cause, drew class diagram with its package. You may noticed OptionGroupSpecification
directly reference OrderOptionGroup
and OptionSpecification
reference OrderOption
from ‘order’ package. This two dotted lines create dependency from ‘store’ package to ‘order’ package.
Use abstraction to break dependency
The code of how we can refactoring with abstract can be found here
https://github.com/zeroFruit/oop-dependency/tree/master/ecommerce-01
To fix this problem, we are going to use abstraction. Instead of directly referencing ‘order’ package’s class, reference abstract class which is in the ‘store’ package.
Be cautious of the word of ‘abstract’. It is not abstract
keyword in java programming language nor interface
. I used this term to mean ‘not changed much’, ‘summery of point’, ‘concentrates on essential’.
@Data
public class Option {
private String name;
private Money price;
@Builder
public Option(String name, Money price) {
this.name = name;
this.price = price;
}
}
@Data
public class OptionGroup {
private String name;
private List<Option> options;
@Builder
public OptionGroup(String name, List<Option> options) {
this.name = name;
this.options = options;
}
}
This is our abstract class. There’s no abstract
, interface
keyword. Because Option
and OptionGroup
have essential fields for representing option and option group, we can say that Option
, OptionGroup
class is much abstracted than OptionSpecification
, OptionGroupSpecification
class in ‘store’ package or OrderOption
, OrderOptionGroup
class in ‘order’ package.
With these abstracted class, both ‘order’, ‘store’ domain implement validation logic, also increased reusability.
Result




This diagram shows result of refactoring. Now Order
, OrderOption
depends on OptionGroup
, Option
class in ‘store’. As a result, by creating abstract class and depends on it, we can remove cyclic dependency.
The way of how we refactor this module is somewhat about ‘Dependency Inversion Principle’.
The idea of this principle is that when designing the interaction between a high-level module and a low-level, the interaction should be done by abstraction between them.
In other words, high-level modules should be easily reusable and unaffected by changes in low-level modules. Also when designing low-level modules, developer should keep in mind with the interaction.
Still there’s some problem in this design, we are going to handle that in the next post.
Conclusion
One important thing when we design software architecture is dependency. Because how we make dependency or relationship between components makes grate impact on the system. And as a developer this can cause fragile codes: one component’s simple change can propagate to other components and break their codes.
There are two kinds of relationship between component: ‘association’, ‘dependency’. Association creates permanant path between components. If A and B has association and we want to know B’s information, we can get it from A.
One signal whether this architecture is well designed or not can be found by checking whether components are have cyclic dependency between them. Cyclic dependency should be avoided as possible because this creates strong coupling.
One way to break cyclic dependency is creating abstraction and making components which cause cyclic dependents to interact with it. This is kinds of ‘Dependency Inversion Principle’ because modules are interact with abstraction instead of concrete one.