티스토리 뷰

반응형


| 시스템


-

- 시스템 수준에서도 깨끗함을 유지하는 방법


|| 시스템 제작과 시스템 사용을 분리하라


 

- 소프트웨어 시스템은 (애플리케이션 객체를 제작하고 의존성을 서로 연결하는) 준비 과정과

  (준비 과정 이후에 이어지는) 런타임 로직을 분리해야 한다.


||| Main 분리


- 시스템 생성과 시스템 사용을 분리하는 한 가지 방법


- 생성과 관련한 코드는 모두 main이나 main이 호출하는 모듈로 옮기고, 나머지 시스템은 모든 객체가 생성되었고

  모든 의존성이 연결되었다고 가정

-> Application은 main이나 객체가 생성되는 과정을 전혀 모른다.


||| 팩토리


- 객체가 생성되는 시점을 Application이 결정할 필요가 생길 경우 Abstract Factory 패턴 사용


- 객체를 생성하는 시점은 Application이 결정하지만 객체를 생성하는 코드는 Application이 모른다.

-> 객체가 생성되는 구체적인 방법은 main쪽에 있는 ~FactoryImpl 이 안다.


||| 의존성 주입 (Dependency Injection, DI)


- 사용과 제작을 분리하는 강력한 매커니즘 중 하나

- 제어의 역전(Inversion of Control, IoC) 기법을 의존성 관리에 적용한 메커니즘

- 한 객체가 맡은 보조 책임을 새로운 객체에게 전적으로 떠넘긴다. (단일 책임 원칙, SRP)



|| 확장


- 소프트웨어 시스템은 물리적인 시스템과 다르다. 

  관심사를 적절히 분리해 관리한다면 소프트웨어 아키텍처는 점진적으로 발전할 수 있다.


||| 횡단(Cross-Cutting) 관심사


- 원론적으로는 모듈화되고 캡슐화된 방식으로 영속성 방식을 구상할 수 있다.

  하지만, 현실적으로는 영속성 방식을 구현한 코드가 온갖 객체로 흩어진다.


- AOP에서 관점(aspect)이라는 모듈 구성 개념은 

  "특정 관심사를 지원하려면 시스템에서 특정 지점들이 동작하는 방식을 일관성 있게 바꿔야 한다."라고 명시

- 영속성을 예로 들면, 프로그래머는 영속적으로 저장할 객체와 속성을 선언한 후 영속성 책임을 영속성 프로임워크에 위임

  그러면 AOP 프레임워크는 대상 코드에 영향을 미치지 않는 상태로 동작 방식을 변경



|| Cross-Cutting Concerns 해결을 위한 세 가지 방법


-

- 자바에서 사용하는 관점 혹은 관점과 유사한 메커니즘 세 개


||| 자바 프록시


- 단순한 상황에 적합

- 개별 객체나 클래스에서 메너드 호출을 감싸는 경우가 좋은 예


> Bank Application에서 JDK 프록시를 사용하여 영속성을 지원하는 예제

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// Bank.java (suppressing package names...)
import java.utils.*;
 
// The abstraction of a bank.
public interface Bank {
    Collection<Account> getAccounts();
    void setAccounts(Collection<Account> accounts);
}
 
// BankImpl.java
import java.utils.*;
 
// The “Plain Old Java Object” (POJO) implementing the abstraction.
public class BankImpl implements Bank {
    private List<Account> accounts;
 
    public Collection<Account> getAccounts() {
        return accounts;
    }
    
    public void setAccounts(Collection<Account> accounts) {
        this.accounts = new ArrayList<Account>();
        for (Account account: accounts) {
            this.accounts.add(account);
        }
    }
}
// BankProxyHandler.java
import java.lang.reflect.*;
import java.util.*;
 
// “InvocationHandler” required by the proxy API.
public class BankProxyHandler implements InvocationHandler {
    private Bank bank;
    
    public BankHandler (Bank bank) {
        this.bank = bank;
    }
    
    // Method defined in InvocationHandler
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if (methodName.equals("getAccounts")) {
            bank.setAccounts(getAccountsFromDatabase());
            
            return bank.getAccounts();
        } else if (methodName.equals("setAccounts")) {
            bank.setAccounts((Collection<Account>) args[0]);
            setAccountsToDatabase(bank.getAccounts());
            
            return null;
        } else {
            ...
        }
    }
    
    // Lots of details here:
    protected Collection<Account> getAccountsFromDatabase() { ... }
    protected void setAccountsToDatabase(Collection<Account> accounts) { ... }
}
 
// Somewhere else...
Bank bank = (Bank) Proxy.newProxyInstance(
    Bank.class.getClassLoader(),
    new Class[] { Bank.class },
    new BankProxyHandler(new BankImpl())
);
cs

- 프록시로 감쌀 Bank Interface, 논리를 구현하는 POJO(Plain Old Java Object) 정의

- 코드의 '양'과 '크기'는 프록시의 두 가지 단점 (프록시를 사용하면 깨끗한 코드를 작성하기 어렵다)


||| 순수 자바 AOP 프레임워크


- 순수 자바 관점을 구현하는 Spring AOP, 등과 같은 여러 자바 프레임워크는 내부적으로 프록시를 사용

  (대부분의 프록시 코드는 판박이라 도구로 자동화 가능)

- Spring은 비즈니스 논리를 POJO로 구현 (순수하게 도메인에 초점)

  POJO는 엔터프라이즈 프레임워크에 (다른 도메인에도) 의존하지 않는다.


> Spring 2.X 설정 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<beans>
    ...
    <bean id="appDataSource"
        class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="me"/>
    
    <bean id="bankDataAccessObject"
        class="com.example.banking.persistence.BankDataAccessObject"
        p:dataSource-ref="appDataSource"/>
    
    <bean id="bank"
        class="com.example.banking.model.Bank"
        p:dataAccessObject-ref="bankDataAccessObject"/>
    ...
</beans>
cs


> Application에서 (XML 파일에 명시된) DI 컨테이너에게 시스템 내 최상위 객체를 요청

1
2
XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("app.xml"getClass()));
Bank bank = (Bank) bf.getBean("bank");
cs


> 다시 작성된 Bank 객체

- 모든 정보가 annotation 속에 있으므로 코드 자체는 깔끔하고 깨끗하다

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
32
33
34
35
36
37
38
39
40
41
42
43
44
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Collection;
 
@Entity
@Table(name = "BANKS")
public class Bank implements java.io.Serializable {
    @Id @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;
    
    @Embeddable // An object “inlined” in Bank’s DB row
    public class Address {
        protected String streetAddr1;
        protected String streetAddr2;
        protected String city;
        protected String state;
        protected String zipCode;
    }
    
    @Embedded
    private Address address;
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy="bank")
    private Collection<Account> accounts = new ArrayList<Account>();
    public int getId() {
        return id;
    }
    
    public void setId(int id) {
        this.id = id;
    }
    
    public void addAccount(Account account) {
        account.setBank(this);
        accounts.add(account);
    }
    
    public Collection<Account> getAccounts() {
        return accounts;
    }
    
    public void setAccounts(Collection<Account> accounts) {
        this.accounts = accounts;
    }
}
cs


||| AspectJ 관점


- 관심사를 관점으로 분리하는 가장 강력한 도구

- 언어 차원에서 관점을 모듈화 구성으로 지원하는 자바 언어 확장



|| 테스트 주도 시스템 아키텍처 구축



- 코드 수준에서 아키텍처 관심사를 구분할 수 있다면, 진정한 테스트 주도 아키텍처 구축이 가능


* 최선의 시스템 구조는 각기 POJO(or 다른) 객체로 구현되는 모듈화된 관심사 영역(Domain)으로 구성.

  이렇게 서로 다른 영역은 해당 영역 코드에 최소한의 영향을 미치는 관점이나 유사한 도구를 사용해 통합.

  이런 구조 역시 코드와 마찬가지로 테스트 주도 기법을 적용할 수 있음.



|| 의사 결정을 최적화하라



- 때때로 가능한 마지막 순간까지 결정을 미루는 방법이 최선이다 !


* 관심사를 모듈로 분리한 POJO 시스템은 기민함을 제공

  이러한 기민함 덕분에 최신 정보에 기반해 최선의 시점에 최적의 결정을 내리기 쉬워지고 결정의 복잡성도 줄어든다.



|| 명백한 가치가 있을 때 표준을 현명하게 사용하라



* 표준을 사용하면 아이디어와 컴포넌트 재사용, 적절한 경험자 구하기, 좋은 아이디어 캡슐화, 컴포넌트 역기 등이 쉽지만,

  때로는 표준을 만드는 시간이 오래 걸려 업계가 기다리지 못 하고 원래 표준을 제정한 목적을 잊어버리기도 한다.



|| 시스템은 도메인 특화 언어가 필요하다



- 특정한 도메인을 적용하는데 특화된 언어

- 도메인 특화 언어(Domain-Specific Language, DSL)를 사용하면 고차원 정책에서 저차원 세부사항에 이르기까지

  모든 추상화 수준과 모든 도메인을 POJO로 표현할 수 있다.



출처 클린 코드 (Robert C. Martin)




반응형
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday