콘텐츠로 건너뛰기

Software Design from point of dependency Part1

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

what is dependency
what is 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

association diagram
association diagram
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

dependency diagram
dependency diagram
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:

e-commerce basic user flow
  1. Customer orders item with options, for example customer ordered two “Fusion5 ProGlide Power razors” with “4 razor blade” and with color of “white”.
  2. 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:

  1. 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.
  2. As a result if customer order on our service we should check whether ordered product name is matched with our service’s product name.
  3. In addition to product name, we should check ordered product’s option
  4. In our service, each product has minimum order amount, price.
  5. 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:

  1. should compare order item name with product name.
  2. should compare order item option group name with product option group name.
  3. should compare order item option name with product option name
  4. should compare order item price with product price
  5. should check whether our service is available
  6. 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.

e-commerce service class diagram
e-commerce service class diagram

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.

Order has association with OrderItem
Order has association with OrderItem

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.

direction of dependency between objects
direction of relationship between objects

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.

layered architecture diagram
layered architecture diagram

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.

e-commerce service package diagram with dependency
e-commerce service package diagram

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?

e-commerce service domain class diagram
e-commerce service domain class diagram

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

e-commerce service package diagram with abstract class
e-commerce service package diagram with abstract class

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.

Leave a comment