기록은 기억의 연장선

더 많은 것을 기억하기 위해 기록합니다


  • Home

  • Tags

  • Categories

  • Archives

  • Search

[ddd] 애그리거트

Posted on 2019-05-20 | Edited on 2020-11-02 | In ddd | Comments: 0 Comments

애그리거트란?

도메인 모델이 복잡해졌을 때, 개별 객체 수준에서 모델을 바라보면 전반적인 구조나 큰 수준에서 도메인간의 관계를 이해하기 어려워진다
주요 도메인 개념간의 관계를 파악하기 어렵다는 것은 코드를 변경하고 확장하는 것이 어려워진다는 것을 의미한다
상위 수준에서 모델이 어떻게 엮여있는지 알아야 전체 모델을 망가뜨리지 않으면서 추가 요구사항을 모델에 반영할 수 있는데,
세부적인 모델만 이해한 상태로는 코드를 수정하기가 두렵기 때문에 코드 변경을 최대한 회피하는 쪽으로 요구사항을 협의하게 된다
즉, 이러한 문제점을 없애려면 상위 수준에서 모델을 조망할 수 있는 방법이 필요하고, 그것이 바로 애그리거트 이다

애그리거트 예시

  • 애그리거트는 관련된 모델을 하나로 모은 것이기 때문에 한 애그리거트에 속한 객체는 유사하거나 동일한 라이프사이클을 갖는다
  • 애그리거트는 경계를 갖는다
    • 한 애그리거트에 속한 객체는 다른 애그리거트에 속하지 않는다
    • 각 애그리거트는 자기 자신만을 관리할 뿐 다른 애그리거트를 관리하지 않는다
    • 경계를 설정할 때 기본이 되는 것은 도메인 규칙과 요구사항이다
      • 도메인 규칙에 따라 함께 생성되는 구성요소는 한 애그리거트에 속할 가능성이 높다
    • A가 B를 갖는다는 대부분 한 애그리거트이지만, 무조건 한 애그리거트인 것은 아니다
      • 상품과 리뷰가 좋은 예시이다
  • 대부분의 애그리거트는 한 개의 엔티티 객체만 갖는 경우가 많으며 두 개 이상의 엔티티로 구성되는 애그리거트는 드물게 존재한다

애그리거트 루트

애그리거트는 여러 객체로 구성되기 때문에 한 객체만 상태가 정상이어서는 안된다
도메인 규칙을 지키려면 애그리거트에 속한 모든 객체가 정상 상태를 가져야한다

애그리거트에 속한 모든 객체가 일관된 상태를 유지하려면 애그리거트 전체를 관리할 주체가 필요한데, 이 책임을 지는 것이 바로 애그리거트의 루트 엔티티이다
애그리거트에 속한 모든 엔티티는 루트 엔티티에 직접 또는 간접적으로 속한다

  • 애그리거트 루트의 핵심 역할은 애그리거트의 일관성이 깨지지 않도록 하는 것이다

  • 애그리거트 루트는 애그리거트가 제공해야 할 도메인 기능을 구현한다

    • 애그리거트 루트가 제공하는 메서드는 도메인 규칙에 따라 애그리거트에 속한 객체의 일관성이 깨지지 않도록 구현해야 한다
  • 애그리거트 루트가 아닌 다른 객체가 애그리거트에 속한 객체를 직접 변경하면 안된다

    • 이러면 애그리거트 루트가 강제하는 규칙을 적용할 수 없어 모델의 일관성을 깨는 원인이 된다
    1
    2
    ShippingInfo si = order.getShippingInfo();
    si.setAddress(newAddress);

    이는 도메인 규칙을 무시하고 DB 테이블에서 직접 데이터를 수정하는 것과 같다
    그렇다고 이를 막는 로직을 서비스 레이어에서 구현하게 되면, 해당 로직이 여러 서비스 레이어에서 중복될 가능성이 높아진다

  • 습관적으로 작성하는 setter 메서드를 피해야한다

    • 도메인 로직을 도메인 객체가 아닌 응용이나 표현 영역으로 분산되게 만드는 원인이 된다
    • 이렇게 되면 도메인 로직이 한곳에 응집되어 있지 않으므로 코드를 유지보수할때도 시간이 훨씬 많이 들게된다
    • setter만 넣지 않아도 이런 상황을 대부분 방지할 수 있다
  • 밸류 객체는 불변 타입으로 구현한다

애그리거트 루트의 기능 구현

  • 애그리거트 루트는 다른 객체들을 조합해서 기능을 완성한다

  • 기능 실행을 위임하기도 한다

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Order {
    private OrderLines orderLines;
    private Long totalAmounts;

    public void changeOrderLines(List<OrderLine> newOrderLines) {
    orderLines.changeOrderLines(newOrderLines); // delegate
    this.totalAmounts = orderLines.getTotalAmounts();
    }
    }

    class OrderLines { // first-level collection
    private List<OrderLine> orderLines;

    public void changeOrderLines(List<OrderLine> newOrderLines) {
    this.orderLines = newOrderLines;
    }
    }

    Order에 getOrderLines() 같은 메서드가 제공될 경우, 외부에서 OrderLines의 메서드를 직접 호출하면 도메인 로직이 깨질 우려가 있으므로(totalAmounts가 반영안됨), 아래와 같이 변경해줘야 한다

    • OrderLines를 불변으로 선언한다(OrderLines의 changeOrderLines에서 새로운 OrderLines 객체 반환)
    • OrderLines내 changeOrderLines의 접근 제한자를 패키지나 protected 범위를 사용한다(같은 애그리거트라 같은 패키지에 속하므로)

트랜잭션 범위

  • 트랜잭션의 범위는 작을수록 좋다
  • 한 트랜잭션에서 한 애그리거트만 수정하는 것을 권장한다
  • 아래와 같은 경우에는 한 트랜잭션에서 두 개 이상의 애그리거트를 수정하는 것을 고려해볼 수 있다
    • 도메인 이벤트와 비동기를 사용할 수 없을 경우(보통 두 애그리거트 수정이 필요하면 이 방식을 이용한다)
    • UI 구현의 편리(한 화면에서 여러 상태를 변경하고 싶을 때)
  • 한 트랜잭션에서 두 개 이상의 애그리거트를 수정해야 한다면 애그리거트에서 다른 애그리거트를 직접 수정하지 말고 응용 서비스에서 두 애그리거트를 수정하도록 구현해야한다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // bad
    @Transactional
    public void doSomething(boolean someFlag) { // 응용 계층
    Order order = orderRepository.findOne(id);
    order.doSomething();
    }

    public void doSomething(boolean someFlag) { // 도메인 계층
    if(someFlag) {
    order.getProduct().doSomething(); // 다른 애그리거트의 도메인 로직을 수행함
    }
    }

    // good
    @Transactional
    public void doSomething(OrderId id, boolean someFlag) {
    Order order = orderRepository.findOne(id);
    order.doSomething();

    if(someFlag) {
    order.getProduct().doSomething(); // 응용 계층에서 다른 도메인 로직을 수행함
    }
    }

리포지터리와 애그리거트

애그리거트는 개념상 완전한 한 개의 도메인 모델을 표현하므로, 객체의 영속성을 처리하는 리포지터리는 애그리거트 단위로 존재한다

  • 별도 DB 테이블에 저장한다고 해서 리포지터리를 따로 만들지 않는다
  • Order가 애그리거트 루트이고, OrderLine은 애그리거트 구성요소이므로 리포지터리는 Order만 존재한다

애그리거트는 개념적으로 하나이므로 리포지터리는 애그리거트 전체를 저장소에 영속화해야 한다

1
2
3
4
5
// 애그리거트 전체 영속화
orderRepository.save(order);

// 완전한 애그리거트 제공
Order order = orderRepository.findById(orderId);
  • 애그리거트 전체(OrderLine 등 포함)를 영속화해야 한다
  • 완전한 order 애그리거트를 제공해야 한다 == OrderLine 등을 전부 제공해야 한다
    • 완전한 애그리거트가 아니라면 NullPointerException 같은 문제가 발생한다(OrderLine이 비어있는둥)
  • 애그리거트를 영속화하는 저장소로 무엇을 사용하든지간에 애그리거트의 상태가 변경되면 모든 변경을 원자적으로 저장소에 반영해야한다

애그리거트간 참조

애그리거트의 관리 주체는 애그리거트 루트이므로, 애그리거트를 참조한다는 것은 애그리거트 루트를 참조한다는 것과 같다

1
2
3
4
5
6
7
8
class Orderer { // Order 애그리거트 구성요소
private Member member;
private String name;
}

class Member { // 다른 애그리거트의 루트

}

이런식으로 직접 다른 애그리거트 루트를 참조하는 것이 편리하긴하나, 다음과 같은 문제점을 야기한다

  1. 편한 탐색의 오용

    한 애그리거트 내부에서 다른 애그리거트에 접근할 수 있는 편리함 때문에 다른 애그리거트를 수정하고자 하는 유혹에 빠지기 쉽다

  2. 성능에 대한 고민

    JPA를 사용할 경우 즉시로딩을 사용해야할지 지연로딩을 사용해야할지 고민해야 한다

  3. 확장 어려움

    트래픽이 증가하여 도메인별로 시스템을 분리했을때(다른 저장소 사용, 다른 기술 사용 등) 문제가 된다
    분리된 시스템에서 몽고DB를 사용할 경우 JPA 같은 단일 기술을 사용할 수 없다

ID를 이용하여 다른 애그리거트를 참조하면 위의 문제점을 완화할 수 있다

1
2
3
4
5
6
7
8
9
10
11
12
class Order {
private Orderer orderer; // 객체 참조
}

class Orderer {
private MemberId memberId; // ID 참조
private String name;
}

class Member {
private MemberId memberId;
}
  • 애그리거트끼리는 ID 참조로 연결되고, 애그리거트에 속한 객체들끼리는 객체 참조로 연결된다
  • 애그리거트간 경계를 명확히 하고 애그리거트간 물리적 연결을 제거하기 때문에 모델의 복잡도를 낮춰준다
  • 애그리거트간 의존이 제거되므로 응집도가 높아지는 효과도 있다

이제 다른 애그리거트가 필요하면 응용 서비스에서 아이디를 이용해서 로딩하면 된다

1
2
3
4
5
6
7
8
9
10
@Transactional
public void doSomething(OrderId id, boolean someFlag) {
Order order = orderRepository.findOne(id);
order.doSomething();

if(someFlag) {
Member member = memberRepository.findById(order.getOrderer().getMemberId());
member.doSomething();
}
}

이렇게 됨으로써

  • 한 애그리거트에서 다른 애그리거트를 수정하는 문제를 원천적으로 방지할 수 있고
  • 즉시 로딩을 할지 지연 로딩을 할 지 걱정하지 않아도 되며
  • 애그리거트별로 다른 구현 기술을 사용하는 것도 가능해진다

ID를 이용한 참조와 조회 성능

// TODO
이 부분은 보완이 필요하다
동기는 이해했으나 구현을 이해하지 못하겠다
View는 어느 영역이라고 봐야하는가?

애그리거트간 집합 연관

1:N 연관

개념적으로는 한쪽 애그리거트에 컬렉션으로 연관을 만든다

1
2
3
class Category {
private Set<Product> products;
}

근데 이런식으로 1:N을 구현에 반영하는 것이 요구사항을 충족하는 것과 상관없는 경우가 종종 있다
위의 경우만 해도, 카테고리내의 상품들을 보여주는 경우 보통 페이징이 들어가게 된다

즉, 아래와 같이 구현된다는 것을 의미한다

1
2
3
4
5
6
7
8
class Category {
private Set<Product> products;

public List<Product> getProducts(int page, int size) {
List<Product> sortedProducts = sortById(products);
return sortedProducts.subList((page - 1) * size, page * size);
}
}

이렇게 구현할 경우 Category 내의 모든 Product를 들고와서 필터하는 것이 되므로, 성능상 심각한 문제가 발생한다
그러므로 보통 이렇게 구현하는 경우는 드물고, 상품 입장에서 자신이 속한 카테고리를 N:1로 연관지어 구한다

1
2
3
class Product {
private CategoryId categoryId;
}

그리고 응용서비스에서 이를 이용하여 Product 목록을 구한다

1
2
3
4
5
6
7
class ProductService {
public Page<Product> getProductOfCategory(Long categoryId, int page, int size) {
Category category = categoryRepository.findById(new CategoryId(categoryId));

return productRepository.findByCategoryId(category.getId(), PageRequest.of(page, size)); // spring data jpa 기능 사용
}
}

연관은 필요하지만 성능때문에 리포지터리에서 구현했다고 생각하면 될까?

N:M 연관

개념적으로는 양쪽 애그리거트에 컬렉션으로 연관을 만든다
하지만 이것도 1:N 관계처럼 요구사항을 고려한 뒤 이 구현을 포함시킬지 말지를 결정해야 한다

보통 특정 카테고리에 속한 상품 목록을 보여줄 때

  • 목록 화면에서 각 상품이 속한 모든 카테고리를 표시하지 않고 (==카테고리에서 상품으로의 집합 연관은 필요없다)
  • 상품 상세에서 제품이 속한 모든 카테고리를 보여준다 (==상품에서 카테고리로의 집합 연관은 필요하다)

이부분 또한 성능때문에 카테고리에서 상품으로의 연관은 고려하지 않는다

즉, 카테고리로의 단방향 N:M 연관만 적용하면 된다

1
2
3
4
5
6
7
8
9
10
11
class Product {
@EmbeddedId
private ProductId id;

@ElementCollection
@CollectionTable(
name = "product_category",
joinColumns = @JoinColumn(name = "product_id")
)
private Set<CategoryId> categoryIds; // ID 참조
}

근데 이렇게하면 Product에서 Category를 조회하는(상품 상세) 부분에서는 N+1이 일어나지 않는가?

Product 목록을 구해오는 리포지터리 부분만 N:M에 맞게 변경해주면 된다
나는 Spring Data Jpa를 썼다고 가정하므로 메서드명만 수정하였다

1
2
3
4
5
6
7
class ProductService {
public Page<Product> getProductOfCategory(Long categoryId, int page, int size) {
Category category = categoryRepository.findById(new CategoryId(categoryId));

return productRepository.findByCategoryIdsIn(category.getId(), PageRequest.of(page, size)); // spring data jpa 기능 사용
}
}

애그리거트를 팩토리로 사용하기

정지된 상점이 아닐 경우 상품을 추가해주는 로직이다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ProductService {
public ProductId registerNewProduct(NewProductRequest req) {
Store store = storeRepository.findById(req.getStoreId());

if(store.isBlocked()) {
throw new IllegalStateException();
}

Product product = new Product(store.getId(), /** 속성들 **/);
productRepository.save(product);

return product.getId();
}
}

다시보면 도메인 기능이 응용 서비스에 노출되어 있음을 알 수 있다
상점이 상품을 생성할 수 있는지 여부를 판단하고 상품을 생성하는 것은 논리적으로 하나의 도메인 기능이기 때문이다

이 기능을 구현하기에 더 좋은 장소는 Store 애그리거트이다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Store {
public Product createProduct(/** 속성들 **/) {
if(isBlocked()) {
throw new IllegalStateException();
}

return new Product(/** 속성들 **/);
}
}

class ProductService {
public ProductId registerNewProduct(NewProductRequest req) {
Store store = storeRepository.findById(req.getStoreId());

Product product = store.createProduct(/** 속성들 **/);
productRepository.save(product);

return product.getId();
}
}

이로써 Product 생성 가능 여부를 확인하는 도메인 로직을 변경해도 도메인 영역의 Store만 변경하면 되고 응용 서비스는 영향을 받지 않게 된다 == 도메인의 응집도가 높아졌다

애그리거트를 팩토리로 사용할 때 얻을 수 있는 장점이다

이처럼 애그리거트가 갖고 있는 데이터를 이용해서 다른 애그리거트를 생성해야 한다면 애그리거트에 팩토리 메서드를 구현하는 것을 고려해보는 것이 좋다

Product의 경우 Store의 식별자와 속성들을 필요로 하는데, Store에서 이를 팩토리 메서드로 구현함으로써 필요한 데이터의 일부를 직접 제공하면서 중요한 도메인 로직을 구현할 수 있었다

참고 : 최범균, 『DDD Start!』, 지앤선(2016)

Read more »

메서드 이동

Posted on 2019-05-17 | Edited on 2020-11-02 | In refactoring | Comments: 0 Comments

메서드가 자신이 속한 클래스보다 다른 클래스의 기능을 더 많이 이용할 땐
그 메서드가 제일 많이 이용하는 클래스 안에서 비슷한 내용의 새 메서드를 작성하자
기존 메서드는 간단히 대리(위임) 메서드로 전환하든지 아예 삭제하자

동기

  • 클래스에 기능이 너무 많거나 클래스가 다른 클래스와 과하게 연동되어 의존성이 지나칠 경우에는 메서드를 옮기는 것이 좋다
  • 메서드가 자신이 속한 객체보다 다른 객체를 더 많이 참조하는 경우에도 메서드를 옮기는 것이 좋다
  • 옮길만한 메서드가 발견되면 그 메서드를 호출하는 메서드, 그 메서드가 호출하는 메서드, 상속 계층에서 그 메서드를 재정의하는 메서드를 살펴본다
  • 판단이 힘들다면 직감에 따라 판단하고, 나중에 판단을 변경해도 된다

방법

  1. 원본 클래스의 원본 메서드에 사용된 모든 기능을 검사해서 그 기능들도 전부 옮겨야할지 판단한다
    • 옮길 메서드에만 사용되는 기능일 경우 그 메서드와 함께 옮겨야한다
  2. 원본 클래스의 하위 클래스와 상위 클래스에서 그 메서드에 대한 다른 선언이 있는지 검사하자
    • 오버라이딩 등
  3. 그 메서드를 대상 클래스안에 선언하고, 원본 메서드의 코드를 복사하고, 잘 돌아가게끔 수정한다
    • 메서드 이름을 적절히 바꿔도 된다
    • 원본 클래스의 변수를 사용한다면 매개변수로 던져주도록 한다
    • 옮길 원본 클래스내에 원본 클래스의 다른 메서드가 들어있거나 원본 클래스의 변수를 2개 이상 사용한다면, 원본 클래스를 매개변수로 던진다
      • 근데 이 상태면 메서드를 이동하는게 맞는지 다시 한번 생각해봐야한다
    • 예외처리 코드가 들어있다면 예외를 논리적으로 어느 클래스가 처리할 지 정해야한다
  4. 원본 클래스에서 옮겨진 대상 클래스 메서드를 참조할 방법을 정한다
    • 원본 메서드를 삭제한다
      • 찾아바꾸기 기능을 사용하면 빠르게 할 수 있다
    • 위임 메서드를 사용한다
      • 참조가 많을땐 이 방법이 더 편하다

예시

아래에서 moveMethod를 B 클래스로 옮겨야 하는 상황이라고 가정한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A {
private int someVar;
private B b;

public void moveMethod() {
if(b.someCondition()) {
// do something
someVar;
// do something
}
}

public void someMethod() {
// do something
moveMethod();
// do something
}
}

moveMethod를 B 클래스로 옮기고 적절히 수정한다

1
2
3
4
5
6
7
8
9
class B {
public void moveMethod(int someVar) {
if(b.someCondition()) {
// do something
someVar;
// do something
}
}
}

여기서 적절한 수정이란, moveMethod를 B 클래스로 옮겼을 떄 여전히 필요한 A 클래스의 기능에 대한 것이다
여기선 A 클래스의 인스턴스 변수로 있었던 someVar가 된다

이럴경우 원본 클래스(A 클래스)의 기능을 사용하려면 아래 4가지 중 하나를 실시하면 된다

  • 그 기능을 대상 클래스로 옮긴다
  • 대상 클래스에서 원본 클래스로의 참조를 생성하여 사용한다
  • 원본 객체를 대상 객체 메서드의 매개변수로 전달한다
  • 그 기능이 변수라면 변수를 매개변수로 전달한다

지금은 그 기능이 변수였기 때문에 4번째 방법을 사용했다
만약 A 클래스의 다른 메서드를 사용해야하거나 참조하는 인스턴스 변수가 2개 이상이라면 클래스 자체를 매개변수로 전달해야한다

이제 A 클래스에서 원본 메서드를 삭제하거나 위임하는 방식으로 변경한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class A {
private int someVar;
private B b;

public void moveMethod() {
b.moveMethod(someVar);
}

public void someMethod() {
// do something
moveMethod();
// do something
}
}

// 또는

class A {
private int someVar;
private B b;

public void someMethod() {
// do something
b.moveMethod(someVar);
// do something
}
}

참고 : 마틴 파울러, 『리팩토링』, 김지원 옮김, 한빛미디어(2012)

Read more »

[spring] AOP

Posted on 2019-05-06 | Edited on 2020-11-02 | In spring | Comments: 0 Comments

메서드 전반적으로 공통기능을 넣고싶다

특정 메서드들에 공통된 기능을 넣고 싶을 경우(로깅, 트랜잭션 등등)가 있다
해당 기능들을 직접 메서드에 넣으면 클래스의 관심사가 1개 이상이 되는 문제가 있으니 이를 분리해야 한다

https://jojoldu.tistory.com/69?category=635883
상속 적용

  1. 비즈니스 로직을 추상메서드로 가지는 부모클래스를 생성함
  2. 추상클래스 앞 뒤로 before(), after() 같은 메서드들을 실행함
  3. 하위 클래스에서 추상 메서드를 오버라이드 한다
    • 단일 책임 원칙을 지킬 수 있게 됨

괜찮은 방법이긴 하지만 상속의 고질적인 문제점을 그대로 가져가게 됨

https://jojoldu.tistory.com/70?category=635883
데코레이터 패턴 적용

  1. 인터페이스를 하나 만듦(e.g. UserService)
  2. UserService를 구현하고, 비즈니스 로직을 구현하는 구현체를 만듦(e.g. UserServiceImpl)
  3. UserService를 구현하고, 공통 기능을 위한 구현체를 만듦(e.g. UserPerformenceServiceImpl)
  4. 3번 객체의 인자로 2번 객체를 가짐
    • UserPerformanceServiceImpl에서 UserServiceImpl을 DI 받음
  5. UserService로 DI받으면 UserPerformanceImpl이 injection 됨
  6. UserController -> UserService(UserPerformanceServiceImpl -> UserServiceImpl) 의 형태로 수행됨

이후 여러가지 방식이 시도됨

AOP란?

https://jojoldu.tistory.com/71?category=635883
AOP = Aspect Oriented Programming(관점 지향 프로그래밍)

애플리케이션 전체에 걸쳐 사용되는 기능을 재사용하도록 지원하는 것

부가기능(로깅, 트랜잭션)이라는 관점에서 어플리케이션을 바라보고, 횡단 관심사를 잘라냄(크로스 컷팅)
그리고 이를 모듈화하여 재사용하고자 함

AOP 용어

  • 타겟

    부가기능을 부여할 대상

  • 애스펙트

    부가기능 모듈을 뜻함
    어드바이스 + 포인트컷

  • 어드바이스

    실질적인 부가기능을 가지고 있는 모듈
    무엇을, 언제를 포함하고 있음
    특정 오브젝트에 종속되지 않음

  • 포인트컷

    부가기능(어드바이스)가 적용될 대상을 선정하는 룰

  • 조인포인트

    어드바이스가 적용될 위치
    스프링은 기본적으로 메서드 조인포인트만 지원함(프록시 방식이기 떄문)
    다른 조인포인트를 사용하고 싶다면 AspectJ 같은 것을 써야함(byte instrument?)

  • 위빙

    타겟에 애스펙트가 적용되어 프록시 객체가 생성되는 과정

적용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Aspect
public class Performance {

@Around("execution(* com.blogcode.board.BoardService.getBoards(..))")
public Object calculatePerformanceTime(ProceedingJoinPoint proceedingJoinPoint) {
Object result = null;
try {
long start = System.currentTimeMillis();
result = proceedingJoinPoint.proceed();
long end = System.currentTimeMillis();

System.out.println("수행 시간 : "+ (end - start));
} catch (Throwable throwable) {
System.out.println("exception! ");
}
return result;
}
}

스프링 부트에서는 몇가지 설정을 추가한 후 이렇게 애스펙트를 정의하면 간단히 적용가능하다

어드바이스

  1. @Before (이전)
  2. @After (이후)
  3. @AfterReturning (정상적 반환 이후)
  4. @AfterThrowing (예외 발생 이후)
  5. @Around (메소드 실행 전후)
    • procceed()를 꼭 호출해줘야함

포인트컷

종류는 위 글 마지막 부분 참조

expression 사용법은 아래와 같다

1
execution([접근제한자] 리턴타입 [클래스타입(패키지포함).]메서드이름(타입 | "..", ...) [throws 예외])

메서드의 풀 시그니처를 문자열로 비교한다고 보면 된다.
getMethod를 통해 메서드 풀 시그니쳐를 뽑아보면 아래와 같다.

1
public int springbook.learningtest.spring.pointcut.Target.minus(int,int) throws java.lang.RuntimeException

확장

https://jojoldu.tistory.com/72?category=635883
@PointCut 어노테이션을 이용해서 어노테이션을 메서드로 정의하고, 재사용할 수 있음

Read more »

[linux] 파일 날짜 변경

Posted on 2019-05-06 | Edited on 2020-11-02 | In linux | Comments: 0 Comments

https://webdir.tistory.com/151

현재시간 : touch -c
지정시간 : touch -t YYYYMMDDhhmm
동기화 : touch -r oldfile newfile

Read more »

필드 자체 캡슐화

Posted on 2019-05-06 | Edited on 2020-11-02 | In refactoring | Comments: 0 Comments

필드에 직접 접근하던 중 그 필드로의 결합에 문제가 생길 땐,
그 필드용 읽기/쓰기 메서드를 작성해서 두 메서드를 통해서만 필드에 접근하게 만들자

동기

  • 변수 직접 접근파 VS 변수 간접 접근파
    • 직접 접근파 : 변수가 정의되어 있는 클래스 안에서는 변수에 자유롭게 접근할 수 있어야 한다
    • 간접 접근파 : 클래스 안에서라도 반드시 접근 메서드를 통해서만 접근 가능해야 한다
  • 직접 접근의 장점
    • 코드를 알아보기 쉽다
  • 간접 접근의 장점
    • 하위 클래스에서 해당 속성을 가져오는 방식을 재정의 할 수 있다(getter override)
    • 속성 초기화를 사용 시점으로 미룰 수 있다
  • 처음에는 직접 접근을 사용하다가 이상한 점이 생길 때 간접 접근으로 바꾸는게 좋다
    • 상위 클래스의 변수를 하위 클래스에서 계산된 값으로 재정의 해야한다거나
    • 사용하는 시점에 속성을 초기화하고 싶다거나
    • 등등등

방법

  1. 필드의 읽기 메서드와 쓰기 메서드를 작성한다
  2. 필드를 참조하는 부분을 전부 찾아서 읽기 메서드와 쓰기 메서드로 고친다
  3. 필드를 private으로 만든다

예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class SomeClass {
private int a;
private int b;
private int c;

public void doSomething() {
setC(getA() + getB());
}

public int returnSomething(boolean flag) {
if(flag) {
return getA() + getB();
}

return getC();
}
}

필드 자체 캡슐화를 할 때는 생성자 안에 쓰기 메서드를 사용하는 것을 주의해야한다
대체로 쓰기 메서드는 객체가 생성된후에 사용하므로, 쓰기 메서드에 초기화 시점과 다른 기능이 추가됐을 수 있다고 전제하는 경우가 많기 때문이다

참고 : 마틴 파울러, 『리팩토링』, 김지원 옮김, 한빛미디어(2012)

Read more »

[ddd] 아키텍쳐

Posted on 2019-05-04 | Edited on 2020-11-02 | In ddd | Comments: 0 Comments

4개의 영역

아키텍쳐를 설계할 때 출현하는 전형적인 영역은 아래와 같다

  • 표현

    HTTP 요청을 응용 영역이 필요로 하는 형식으로 변환해서 응용 영역에 전달하고, 응용 영역의 응답을 HTTP 응답으로 변환해서 전송한다
    e.g. 요청 파라미터를 객체로 받고 결과를 JSON으로 리턴

  • 응용

    시스템이 사용자에게 제공해야 할 기능을 구현한다

    • 응용 영역은 기능을 구현하기 위해 도메인 영역의 도메인 모델을 사용한다
    • 응용 서비스는 로직을 직접 수행하기보다는 도메인 모델에 로직 수행을 위임한다
  • 도메인

    도메인의 핵심 로직을 구현한다
    e.g. 주문 도메인의 경우 ‘배송지 변경’, ‘결제 완료’ 같은 핵심 로직을 도메인 모델에서 구현한다

  • 인프라스트럭쳐

    구현 기술에 대한 것을 다룬다
    e.g. RDBMS 연동, 몽고 DB, 메시지 큐 전송 등

계층 구조 아키텍쳐

1
2
3
4
5
6
7
   표현
↓
응용
↓
도메인
↓
인프라스트럭쳐
  • 계층 구조는 상위 계층에서 하위 계층으로의 의존만 존재하고 하위 계층에서 상위 계층에 의존하지는 않는다
  • 계층 구조를 엄격하게 적용하면 상위 계층은 바로 아래 계층에만 의존을 가져야하지만, 구현의 편리함을 위해 계층 구조를 유연하게 적용한다

이 말인 즉, 표현, 응용, 도메인 계층이 상세한 구현 기술을 다루는 인프라스트럭쳐에 의존할 수 있다는 점이다
예를 들면 아래처럼 될 수 있다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class CalculateDiscountService { 
private DroolsRuleEngine ruleEngine = new DroolsRuleEngine();

public Money calculateDiscount(List<OrderLine> orderLines, String customerId) {
Customer customer = findCustomer(customerId);

// 초기 돈
MutableMoney money = new MutableMoney(0);

// 조건들 추가하고
List<?> facts = Arrays.asList(customer, money);
facts.addAll(orderLines);

// DroolsRulsEngine을 이용해 할인율 적용
ruleEngine.evaluate("discountCalculation", facts);

return money.toImmutableMoney();
}
}

위 처럼 도메인에 메시지를 보내는 것 외에 특정 엔진을 사용해야하는 상황이다
(특정 엔진을 사용하는 것이 더 나은 상황)

하지만 위 코드는 아래 2가지 문제점을 가지고 있다

  • 테스트하기 어렵다
    • DroolsRuleEngine이 완벽하게 동작해야만 CalculateDiscountService를 테스트할 수 있다
  • 구현 방식을 변경하기 어렵다
    • DroolsRuleEngine이 아니라 다른 엔진을 사용하도록 변경하고자 한다면 많은 부분이 변경되어야 할 것이다

고수준 모둘이 제대로 동작하려면 저수준 모듈을 사용해야 하는데, 인프라스트럭쳐의 경우 특정 기술을 직접 구현하므로 이런 문제점이 발생하게 된다.
이를 어떻게 처리할 수 있을까?

DIP

정답은 저수준 모델이 고수준 모델에 의존하도록 바꾸는 것이다
다시 한번 CalculateDiscountService를 살펴보면, discount를 얻는데 어떤 엔진을 사용했느냐는 중요하지 않다
단지 고객정보와 구매정보에 룰을 적용해서 할인 금액을 구한다는 것이 중요할 뿐이다
이 부분을 추상화해서 인터페이스로 만들 수 있다

1
2
3
interface RuleDiscounter {
public Money applyRules(Customer customer, List<OrderLine> orderLines);
}

이 인터페이스를 사용하여 CalculateDiscountService에서 DroolsRuleEngine을 제거할 수 있다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CalculateDiscountService { 
private RuleDiscounter ruleDiscounter;

public CalculateDiscountService(RuleDiscounter ruleDiscounter) {
this.ruleDiscounter = ruleDiscounter;
}

public Money calculateDiscount(List<OrderLine> orderLines, String customerId) {
Customer customer = findCustomer(customerId);
return ruleDiscounter.applyRules(customer, orderLines);
}
}

class DroolsRuleDiscounter implements RuleDiscounter {
// ...
}

CalculateDiscountService는 더 이상 구현기술인 Drools(저수준)에 의존하지 않고,
룰을 이용한 할인 금액 계산 을 표현하는 RuleDiscounter 인터페이스(고수준)에 의존한다

저수준이 고수준에 의존

그림에서 보이다시피 고수준 모듈이 저수준 모듈을 사용함에도 불구하고 저수준 모듈이 고수준 모듈에 의존하고 있다
이를 DIP(Dependency Inversion Principle, 의존 역전 원칙) 이라고 부른다

그리고 인터페이스를 구현한 저수준 모듈은 외부에서 생성해 주입(Dependency Injection) 해주게 된다

이와 같이 DIP를 적용함으로써 기존의 고수준 모듈에서 저수준 모듈 사용에서 오던 문제점들을 해결할 수 있게 된다

  1. 테스트하기 쉬워진다
    • 특정 클래스가 아니라 인터페이스에 의존하므로 mockito를 사용한 stub 등을 사용한다면 직접 구현체를 구현하지 않고도 테스트를 진행할 수 있게 된다
  2. 구현 방식을 변경하기 숴워진다
    • 저수준 모듈에 강하게 결합되어 있는 구조가 아니기 때문에, 구현 방식을 변경하고 싶다면 인터페이스를 구현한 구현체를 하나 더 만들어서 DI 해주면 CalculateDiscountService의 코드변경 없이 구현 방식을 변경할 수 있다(OCP)

DIP 주의사항

잘못된 DIP

DIP 결과 구조만 보고 인터페이스를 잘못 추출한 결과이다
RuleEngine은 고수준 모델인 도메인 관점이 아니라 엔진이라는 저수준 모듈 관점에서 도출된 것이다
즉, 여전히 고수준 모듈이 저수준 모듈에 의존하고 있는 셈이다

DIP를 적용할 때 하위 기능을 추상화한 인터페이스는 고수준 모듈 관점에서 도출해야 한다

도메인 영역의 주요 구성요소

  1. 엔티티

    고유의 식별자를 가지고 자신의 라이프 사이클을 갖는 객체
    데이터와 데이터와 관련된 기능을 함께 제공한다

  2. 벨류

    고유의 식별자를 갖지 않고 주로 도메인 객체의 속성을 표현할 떄 사용되는 객체
    다른 벨류 타입의 속성으로도 사용될 수 있다

  3. 애그리거트

    관련된 엔티티와 벨류 객체를 개념적으로 하나로 묶은 것

  4. 리포지터리

    도메인 모델의 영속성을 처리함

  5. 도메인 서비스

    특정 엔티티에 속하지 않은 도메인 로직을 제공함
    도메인 로직이 여러 엔티티와 벨류를 필요로 할 경우 여기에서 로직을 구현한다

엔티티와 벨류

도메인 모델의 엔티티와 DB 모델의 엔티티는 다르다

  • 도메인 모델의 엔티티는 단순히 데이터를 담고 있는 데이터 구조라기보다는 데이터와 함께 기능을 제공하는 객체이다
    • 도메인 관점에서 기능을 구현하고 기능 구현을 캡슐화해서 데이터가 임의로 변경되는 것을 막는다
  • 도메인 모델의 엔티티는 두 개 이상의 데이터가 개념적으로 하나인 경우 벨류 타입을 이용해서 표현할 수 있다
    • RDBMS는 벨류를 제대로 표현하기 힘들다

      ORDER_NAME, ORDER_EMAIL 필드로 표시하거나 또는 ORDER_ORDERER 테이블로 표시하더라도 딱히 벨류 타입의 느낌을 주지 못한다

애그리거트

도메인이 커질수록 개발할 도메인 모델도 커지게 되고, 많은 엔티티와 벨류가 생기면서 모델이 점점 더 복잡해진다
이렇게 도메인 모델이 복잡해지면 개발자가 전체 구조가 아닌 한개 엔티티와 벨류에 집중하게 되는 경우가 발생한다

지도를 볼 때 매우 상세하게 나온 대축적 지도를 보면 큰 수준에서 어디에 위치하고 있는지 이해하기 어려우므로 큰 수준에서 보여주는 소축적 지도를 함꼐 봐야 현재 위치를 보다 더 정확하게 이해할 수 있다
이와 비슷하게 도메인 모델도 개별 객체가 아니라 상위 수준에서 모델을 볼 수 있어야 전체 모델과 개별 모델을 이해하는데 도움이 된다
이게 바로 애그리거트(AGGREGATE) 이다

주문 애그리거트는 주문, 주문자, 배송정보 등을 포함한다

애그리거트는 군집에 속한 객체들을 관리하는 루트 엔티티를 가진다

  • 루트 엔티티는 애그리거트에 속해 있는 엔티티와 벨류 객체를 이용해서 애그리거트가 구현해야 할 기능을 제공한다
  • 애그리거트를 사용하는 코드는 애그리거트가 제공하는 기능을 실행하고 애그리거트 루트를 통해서 간접적으로 애그리거트 내의 엔티티나 벨류 객체에 접근하게 된다

애그리거트를 구현할 때는 고려할 것이 많다

리포지터리

도메인 객체를 지속적으로 사용하려면 RDBMS 같은 물리적 저장소에 도메인 객체를 보관해야 하고, 이를 위한 도메인 모델이 리포지터리 이다

리포지토리는 애그리거트 단위로 도메인 객체를 조회하고 저장하는 기능을 제공한다

리포지터리는 도메인 객체를 영속화하는데 필요한 기능을 추상화 한 것이기 떄문에 고수준 모듈에 속하고, 실제 구현 클래스는 인프라스트럭쳐 영역에 속한다

리포지터리의 사용 주체는 응용 서비스이다

  • 응용 서비스는 필요한 도메인 객체를 구하거나 저장할 때 리포지터리를 이용한다
  • 그러므로 응용 서비스가 필요로 하는 메서드를 제공한다
    • 기본은 저장과 식별자로 조회 메서드이다

요청 처리 흐름

웹어플리케이션 요청처리 흐름

응용 서비스는 도메인 모델을 이용해서 기능을 구현한다
도메인 객체를 조회해야하거나 새로 생성해야 할 경우 리포지터리를 이용한다

인프라스트럭쳐

표현, 응용, 도메인 영역을 모두 지원하는 영역이다
DIP에서 언급했듯이 인프라스트럭쳐를 직접 사용하는 것 보단 각자의 영역에서 정의한 인터페이스를 인프라스트럭쳐 영역에서 구현하는 것이 시스템을 더 유연하고 테스트하기 쉽게 만들어준다

하지만 무조건 인프라스트럭쳐에 대한 의존을 없애는 것이 좋은 것은 아니다
예를 들면 스프링의 @Transactional이 있다
코드에서 스프링에 대한 의존을 없애려면 복잡한 스프링 설정을 사용해야하는데, 이럴때는 굳이 의존을 없애지 않는 것이 좋다
구현의 편리함 또한 다른 장점들만큼 중요한 부분이기 때문이다

표현 영역 또한 인프라스트럭쳐와 쌍을 이룬다. 그것도 항상.

모듈 구성

기본적으로 우리는 항상 아키텍쳐가 각 영역의 별도 패키지에 위치하는 형태로 구성한다

1
2
3
4
com.bookstore.ui
com.bookstore.application
com.bookstore.domain
com.bookstore.infrastructure

애그리거트의 모델과 리포지터리는 같은 패키지에 위치시킨다

하지만 패키지 구성 방식에 정답이 있는것은 아니다

도메인이 크면 하위 도메인마다 별도 패키지를 구성할 수 있다

1
2
3
4
5
6
7
8
9
10
11
com.bookstore.catelog.ui
com.bookstore.catelog.application
com.bookstore.catelog.domain
com.bookstore.catelog.infrastructure

com.bookstore.order.ui
com.bookstore.order.application
com.bookstore.order.domain
com.bookstore.order.infrastructure

...

만약 카탈로그 도메인이 상품 애그리거트와 카테고리 애그리거트로 구성된다면 domain에서 패키지를 나눌 수 있다

1
2
3
4
5
com.bookstore.catelog.ui
com.bookstore.catelog.application
com.bookstore.catelog.domain.product
com.bookstore.catelog.domain.category
com.bookstore.catelog.infrastructure

만약 도메인 서비스 계층이 따로 있으면 아래와 같이 나눌수도 있다

1
2
3
com.bookstore.catelog.domain.product
com.bookstore.catelog.domain.category
com.bookstore.catelog.domain.service

service는 product와 category 의 도메인 서비스 계층이다

이러한 룰들은 domain 뿐만이 아니라 다른 계층에도 적용될 수 있다
그리고 알다시피, 모듈 구성에 정답은 없다
하지만 가능하면 한 패키지내에 10개 미만으로 타입 개수를 유지하는 것이 좋다

참고 : 최범균, 『DDD Start!』, 지앤선(2016)

Read more »

필드 캡슐화

Posted on 2019-05-03 | Edited on 2020-11-02 | In refactoring | Comments: 0 Comments

public 필드가 있을 땐
그 필드를 private로 만들고 필드용 읽기 메서드와 쓰기 메서드를 작성하자

동기

  • 객체지향의 주요 원칙 중 하나는 캡슐화이다
    • 그러므로 데이터는 절대 public 타입으로 선언되면 안된다
    • 데이터를 public 타입으로 만들면 데이터가 있는 객체가 모르는 사이에 다른 객체가 데이터 값을 읽고 변경할 수 있다
    • 이로 인해 데이터와 기능이 분리된다
  • 데이터와 데이터를 사용하는 기능이 한 곳에 모여있으면 코드를 수정하기 쉽다

방법

  1. 캡슐화 할 필드를 위한 읽기 메서드(getter)와 쓰기 메서드(setter)를 작성한다
  2. 클래스 외부에서 그 필드를 참조하는 모든 부분을 찾아서 1에서 작성한 메서드로 대체한다
  3. 참조하는 부분을 모두 수정했다면 필드를 private으로 변경한다

사실상 getter, setter를 적용하는 것은 public 메서드를 쓸때와 별 다를것이 없고, 문제도 해결되지 않는다
일단 이런식으로 필드 직접 접근을 막은 다음, 불필요한 읽기/쓰기 메서드 호출 부분을 제거해야한다

참고 : 마틴 파울러, 『리팩토링』, 김지원 옮김, 한빛미디어(2012)

Read more »

필드 이동

Posted on 2019-05-03 | Edited on 2020-11-02 | In refactoring | Comments: 0 Comments

어떤 필드가 자신이 속한 클래스보다 다른 클래스에서 더 많이 사용될 때는
대상 클래스 안에 새 필드를 선언하고 그 필드 참조 부분을 전부 새 필드 참조로 수정하자

동기

  • 어떤 필드가 자신이 속한 클래스보다 다른 클래스에 있는 메서드에서 더 많이 참조한다면, 그 필드를 옮기는 것을 생각해봐야 한다
  • 만약 클래스 추출을 할 경우 필드 추출이 우선이고, 메서드 추출이 그 다음이다

방법

책의 예제에는 잘 안나와있는데, 난 아래와 같이 이해했다

  1. A 클래스안에서 B 클래스의 field 필드를 자주 이용한다면 B 클래스의 field를 A 클래스로 옮겨버릴 수 있다

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    class A {
    private B b;
    public void someMethod1() {
    // do something
    b.getField(); // 이런식으로 자주 사용된다면
    // do something
    }

    public void someMethod2() {
    // do something
    b.getField(); // 이런식으로 자주 사용된다면
    // do something
    }

    public void someMethod3() {
    // do something
    b.getField(); // 이런식으로 자주 사용된다면
    // do something
    }
    }

    class B {
    private int field;
    // getter/setter

    public void someMethod() {
    // do something;
    field;
    // do something;
    }
    }
  2. 하지만 B 클래스의 field 필드도 B 클래스내에서 사용하는 부분이 어느정도 있다(고 가정한다). 이럴땐 필드 이동으로 인한 변경을 최소화 하기 위해 이동 대상 클래스 필드(B의 field)에 필드 자체 캡슐화를 적용한다

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class A {
    // ...
    }

    class B {
    private int field;
    // getter/setter

    public void someMethod() {
    // do something;
    getField(); // 필드 자체 캡슐화
    // do something;
    }
    }
  3. A 클래스에 field 필드와 getter/setter를 작성하고, B 클래스를 참조하던 부분을 변경한다

    • 필드 이동 시 getter/setter는 세트로 같이 옮긴다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class A {
    private int field;
    // getter/setter

    public void someMethod1() {
    // do something
    field;
    // do something
    }

    public void someMethod2() {
    // do something
    field;
    // do something
    }

    public void someMethod3() {
    // do something
    field;
    // do something
    }
    }
  4. B 클래스에서 A 클래스의 field를 참조할 방법을 정한다

    • 참조하는 부분에서 직접 a.getField(), a.setField() 로 접근하는 방법
    • 필드 자체 캡슐화한 메서드만 변경하는 방법
      • 2번에서 이미 수행했고, 이 방식이 변경을 가장 최소화 할 수 있다
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      class B {
      private A a;

      public int getField() {
      return a.getField();
      }

      public void someMethod() {
      // do something;
      getField(); // 변경 없음
      // do something;
      }
      }
  5. B 클래스에서 field 필드를 삭제한다

참고 : 마틴 파울러, 『리팩토링』, 김지원 옮김, 한빛미디어(2012)

Read more »

클래스 추출

Posted on 2019-05-03 | Edited on 2020-11-02 | In refactoring | Comments: 0 Comments

두 클래스가 처리해야 할 기능이 하나의 클래스에 들어 있을 땐(하나의 클래스에서 2가지 일 이상을 수행할 땐)
새 클래스를 만들고 기존 클래스의 관련 필드와 메서드를 새 클래스로 옮기자

동기

  • 클래스는 확실하게 추상화되어야 하며, 두세 가지의 명확한 기능을 담당해야 한다
  • 하지만 개발자는 클래스에 점증적으로 어떤 기능이나 데이터를 추가하기 때문에, 클래스는 시간이 갈수록 방대해지기 마련이다
  • 이런 클래스는 너무 많은 메서드와 데이터가 들어있어 이해하기 힘드므로, 분리할 부분을 궁리해서 떼어내야 한다
  • 데이터의 일부분과 메서드의 일부분이 한 덩어리이거나, 주로 함께 변화하거나 서로 유난히 의존적인 데이터의 일부분일 경우 클래스로 떼어내기 좋다

방법

  1. 위의 동기를 참조해서 클래스의 기능 분리 방법을 정한다
  2. 분리할 기능을 넣을 새 클래스를 작성한다
    • 기능이 분리됨으로써 원본 클래스의 이름이 변경될 수 있다
  3. 원본 클래스에서 새 클래스로의 링크를 만든다
    • 필요할 때 까지 역방향 링크를 만들지 않는다
  4. 옮길 필드마다 필드 이동을 적용한다
  5. 메서드 이동을 적용한다
  6. 각 클래스를 다시 검사해서 인터페이스를 줄인다
    • 양방향 링크를 단방향 링크로 바꿀 수 있는지 등
  7. 추출 된 클래스의 공개 범위를 결정한다
    • 공개할 경우 몇가지

예시

아래와 같은 코드가 있다

1
2
3
4
5
6
7
8
9
10
class Person {
private String name;
private String officeAreaCode;
private String officeNumber;
// getter/setter

public String getTelephoneNumber() {
return "(" + officeAreaCode + ")" + officeNumber;
}
}

officeAreaCode와 officeNumber를 합쳐서 TelephoneNumber로 분리하기로 한다
필드 이동을 적용하기 전에 필드 자체 캡슐화를 적용한다

1
2
3
4
5
6
7
8
9
10
class Person {
private String name;
private String officeAreaCode;
private String officeNumber;
// getter/setter

public String getTelephoneNumber() {
return "(" + getOfficeAreaCode() + ")" + getOfficeNumber();
}
}

새로 생성한 클래스에 필드 이동을 적용한다

1
2
3
4
5
class TelephoneNumber {
private String areaCode;
private String number;
// getter/setter
}

Person에서 officeAreaCode, officeNumber를 삭제한다
이때 getter는 위임 코드로 전환한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {
private TelephoneNumber officeTelephone;
private String name;

public String getOfficeAreaCode() {
return officeTelephone.getAreaCode();
}

public String getOfficeNumber() {
return officeTelephone.getNumber();
}

public String getTelephoneNumber() {
return "(" + getOfficeAreaCode() + ")" + getOfficeNumber();
}
}

getTelephoneNumber()에 메서드 이동을 적용한다

1
2
3
4
5
6
7
8
9
class TelephoneNumber {
private String areaCode;
private String number;
// getter/setter

public String getTelephoneNumber() {
return "(" + areaCode + ")" + number;
}
}

Person을 정리한다

1
2
3
4
5
6
7
8
class Person {
private TelephoneNumber officeTelephone;
private String name;

public String getTelephoneNumber() {
return officeTelephone.getTelephoneNumber();
}
}

여기서 TelephoneNumber에 대한 공개 여부에 대한 이야기가 나오는데 잘 이해가 가지 않는다
외부에 공개하면 불변 객체로 만들어서 수정이 불가능하게끔 해야하고,
참조 객체로 만드려면 외부에 공개하지 않아야 하는것이 아닐까?
모르겠당…

참고 : 마틴 파울러, 『리팩토링』, 김지원 옮김, 한빛미디어(2012)

Read more »

DI(Dependency Injection)

Posted on 2019-05-03 | Edited on 2020-11-02 | In etc | Comments: 0 Comments

https://www.slideshare.net/baejjae93/dependency-injection-36867592

OOP는 원래 객체간 메시지 전달을 하는 방식으로 진행되는 프로그래밍 방식아므로, 클래스간 의존은 어쩌피 존재한다.
우리의 목적은 의존을 없애야하는게 아니라 결합을 약하게 해야하는 것이다.

클래스에서 특정 클래스에 직접 의존하는 것이 아니라, 행위(인터페이스)에 의존해야 한다
(클래스에 직접 의존하면 클래스에 대해 더 많이 가정해야 하므로 강한 결합이 형성된다)

행위를 구현한 클래스들은 외부에서 생성해서 주입하도록 한다

근데 매번 이렇게 외부에서 구현체를 생성해서 넣어주는게 귀찮고 불편하므로, 이런걸 제공해주는 프레임워크를 사용하는 것이 좋다 == 스프링
스프링의 최대 장점은 DI이다

Read more »
1234…19

JunYoung Park

182 posts
18 categories
344 tags
RSS
© 2020 JunYoung Park
Powered by Hexo v3.6.0
|
Theme – NexT.Muse v7.1.0