🔖 오늘 읽은 범위 : 11장, 시스템
자바에서 사용하는 관점 혹은 관점과 유사한 메커니즘 세 개를 살펴보자!
// Bank.java (패키지 이름을 감춘다)
import java.util.*;
// 은행 추상화
public interface Bank {
Collection<Account> getAccounts();
void setAccounts(Collection<Account> accounts);
}
// Bankimpl.java
import java.util.*;
// 추상화를 위한 POJO("Plain Old Java Object") 구현
public class Bankimpl implements Bank {
List<Account> accounts;
private 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.*;
// 프록시 API가 필요한 "InvocationHandler"
public class BankProxyHandler implements InvocationHandler {
private Bank bank;
public BankProxyHandler (Bank bank) {
this.bank = bank;
}
// 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 {
...
}
}
//세부사항은 여기에 이어진다.
protected Collection<Account> getAccountsFromDatabase() { … }
protected void setAccountsToDatabase{Collection<Account> accounts) { … }
}
// 다른 곳에 위치하는 코드
Bank bank = {Bank) Proxy.newProxyInstance{
Bank.class.getClassloader(),
new Class[] { Bank.class },
new BankProxyHandler(new Bankimpl()));
Banklmpl
메서드로 매핑한다.XmlBeanFactory bf =
new XmlBeanFactory(new ClassPathResource("app.xml", getClass())) ;
Bank bank = (Bank)bf.getBean("bank") ;
스프링 관련 자바 코드가 거의 필요 없으므로 애플리케이션은 사실상 스프링과 독립적이다.package com.example.banking.model;
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 // Bank의 데이터베이스 행에 '인라인으로 포함된’ 객체
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;
}
}
애플리케이션 도메인 논리를 POJO로 작성할 수 있다면, 즉 코드 수준에서 아키텍처 관심사를 분리할 수 있다면, 진정한 테스트 주도 아키텍처 구축이 가능해진다.
BDUF(Big Design Up Front) 를 추구할 필요가 없다. 실제로 BDUF는 해롭기까지 하다. 처음에 쏟아 부은 노력을 버리지 않으려는 심리적 저항으로 인해, 그리고 처음에 선택한 아키텍처가 향후 사고 방식에 미치는 영향으로 인해, 변경을 쉽사리 수용하지 못하는 탓이다.
소프트웨어 역시 나름대로 형체(physics)가 있지만, 소프트웨어 구조가 관점을 효과적으로 분리한다면, 극적인 변화가 경제적으로 가능하다. 다시 말해, ‘아주 단순하면서도’ 멋지게 분리된 아키텍처로 소프트웨어 프로젝트를 진행해 결과물을 재빨리 출시한 후, 기반 구조를 추가하며 조금씩 확장해 나가도 괜찮다는 말이다.
그렇다고 ‘아무 방향 없이’ 프로젝트에 뛰어들어도 좋다는 소리는 아니다. 프로젝트를 시작할 때는 일반적인 범위, 목표, 일정은 물론이고 결과로 내놓을 시스템의 일반적인 구조도 생각해야 한다. 하지만 변하는 환경에 대처해 진로를 변경할 능력도 반드시 유지해야 한다.
최선의 시스템 구조는 각기 POJO(또는 다른) 객체로 구현되는 모듈화된 관심사 영역(도메인)으로 구성된다. 이렇게 서로 다른 영역은 해당 영역 코드에 최소한의 영향을 미치는 관점이나 유사한 도구를 사용해 통합한다. 이런 구조 역시 코드와 마찬가지로 테스트 주도 기법을 적용할 수 있다.
모듈을 나누고 관심사를 분리하면 지엽적인 관리와 결정이 가능해진다. 도시든 소프트웨어 프로젝트든, 아주 큰 시스템에서는 한 사람이 모든 결정을 내리기 어렵다.
우리는 때때로 가능한 마지막 순간까지 결정을 미루는 방법이 최선이라는 사실을 까먹곤 한다. 게으르거나 무책임해서가 아니다. 최대한 정보를 모아 최선의 결정을 내리기 위해서다.
관심사를 모듈로 분리한 POJO 시스템은 기민함을 제공한다. 이런 기민함 덕택에 최신 정보에 기반해 최선의 시점에 최적의 결정을 내리기가 쉬워진다. 또한 결정의 복잡성도 줄어든다.
표준을 사용하면 아이디어와 컴포넌트를 재사용하기 쉽고, 적절한 경험을 가진 사람을 구하기 쉬우며, 좋은 아이디어를 캡슐화하기 쉽고, 컴포넌트를 엮기 쉽다. 하지만 때로는 표준을 만드는 시간이 너무 오래 걸려 업계가 기다리지 못한다. 어떤 표준은 원래 표준을 제정한 목적을 잊어버리기도 한다.
DSL(Domain Specific Language)은 간단한 스크립트 언어나 표준 언어로 구사한 API를 가리킨다.
좋은 DSL은 도메인 개념과 그 개념을 구현한 코드 사이에 존재하는 ‘의사소통 간극’을 줄여준다.(…) 도메인 전문가가 사용하는 언어로 도메인 논리를 구현하면 도메인을 잘못 구현할 가능성이 줄어든다.
도메인 특화 언어(Domain-Specific Language, DSL)을 사용하면 고차원 정책에서 저차원 세부사항에 이르기까지 모든 추상화 수준과 모든 도메인을 POJO로 표현할 수 잇다.
도시든 소프트웨어 프로젝트든, 아주 큰 시스템에서는 한 사람이 모든 결정을 내리기 어렵다. (p.211)
프록시란 '대리’라는 의미로 프록시에게 어떤 일을 대신 시키는 것이다.
프록시 서버
프록시 패턴
이렇게 하면 구현 클래스에 직접 접근하지 않고, 프록시를 통해 우회해서 접근하여 흐름제어를 할 수 있다.
실제 메소드가 호출되기 이전에 필요한 기능(전처리 등의)을 구현 객체 변경 없이 추가할 수 있다. (코드변경의 최소화, DECORATOR 패턴)
캐시를 사용할 수 있다. (프록시가 내부캐시를 통해 데이터가 캐시에 존재하지 않는 경우에만 주체 클래스에서 작업이 실행되도록 할 수 있다.)
동적 프록시
프록시를 사용하기 위해서는 대상 클래스 수만큼의 프록시 클래스를 만들어줘야 하고, 그 안에 들어가는 반복되는 코드 때문에 코드 중복이라는 단점이 있다.
이러한 단점들을 보완하여 컴파일 시점이 아닌, 런타임 시점에 프록시 클래스를 만들어주는 방식이 동적 프록시이다.
참고 링크: [Java] 프록시패턴 (Proxy Pattern)
참고 링크: [Java] 동적 프록시(Dynamic Proxy)
스프링은 JPA를 표준 인터페이스로 정하면서 그 문제를 해결했다. 즉, ORM 기술을 도입하기 위한 인터페이스를 정의하고, 모든 ORM 기술들은 이 인터페이스 아래서 구현되고 실행된다.
PSA에 대해서는 한번에 이해되지 않아, 다음에 한번 더 보면 좋겠다!
특정 도메인(산업, 분야등 특정 영역)에 특화된 언어
아래 링크의 “우리 주변에 있는 DSL”을 보면 조금 이해가 쉬워진다. 그래도 그 중 많은 것을 아직 써보지 못해 확실하게 이해가 되지는 않는 것 같다..