[DDD] User-Familly 구현

User-Familly 관계부터 생각해보면, User가 일방적으로 여러 Familly를 가지고 있다고 볼 수 있습니다. 다중성(?)은 User에서 Familly쪽으로 1:대 관계입니다. (형제 자매가 같이 입사한 경우 다:다가 될 수도 있지만, 드문 경우니깐 중복 데이터 상관하지 않고 일단은 1대다로 ㄱㄱ, ) 생명주기를 생각해보면, Familly가 User에 종속되어 있습니다. 타입을 생각해보면 User는 엔티티가 거의 확실하고, Familly는 Value 타입에 가까운 듯 합니다. 하지만 편의상 id를 주고 관리하렵니다.

자 그럼 이제 필요한 도메인 클래스를 추가하고 속성들을 만든 다음 관계를 명시합니다.

class User {

    @OneToMany(cascade = { CascadeType.ALL })
    Set<Familly> famillies;

}

여기서 준 @OneToMany로 다중성이 어떤지 나타나고, Familly의 생명주기가 User에 종속적이라는 것이 드러납니다.

다음은 UserController(UC)로 갑니다. FamillyController를 만들 필요 없이, Familly와 연관되는 부분과 관련하여 그 요청을 UC가 처리하게 합니다. 화면상으로는 User의 수정화면에서 Familly 그리드가 보이고, 거기에 추가하는 버튼과, 수정하는 버튼이 달려있습니다.


새로 개선한 OSAF를 사용하여 만든 컨트롤러에 Familly를 그리드, 추가, 수정을 다룰 핸들러 메서드를 추가했습니다.

@Controller
@RequestMapping(“/base/user/*.do”)
public class UserController extends GenericController<User, UserService, UserRef, UserValidator, UserParams>{

    public UserController() {
        super(User.class);
    }

    @Autowired FamillyValidator famillyValidator;

    @RequestMapping
    public void famillygrid(@RequestParam int userid, ModelMap model, OrderPage orderPage){
        if (orderPage.getOrder() == null) orderPage.setOrder(“name asc”);
        model.addAttribute(“list”, service.findFamilliesBy(userid));
        model.addAttribute(“orderPage”, orderPage);
    }

    @RequestMapping(value=”/base/user/famillyadd.do”, method=RequestMethod.GET)
    public void famillyadd(ModelMap model){
        model.addAttribute(“model”, new Familly());
    }

    @RequestMapping(method=RequestMethod.POST)
    public String famillyadd(int userid, @ModelAttribute(“model”) Familly familly, BindingResult result,
            SessionStatus status){
        famillyValidator.validate(familly, result);
        if (result.hasErrors())
            return “/base/user/famillyadd”;
        else {
            service.addFamilly(userid, familly);
            status.setComplete();
            return CommonPages.CLOSE_GRID_REFRESH;
        }
    }

    @RequestMapping(value=”/base/user/famillyupdate.do”, method=RequestMethod.GET)
    public void famillyupdate(int famillyid, ModelMap model){
        model.addAttribute(“model”, service.findFamillyById(famillyid));
    }

}

thin facade 역할을 하고 있는 service를 이용하고 있습니다.

@Service
@Transactional
public interface UserService extends GenericService<User, UserParams>{

    Set<Familly> findFamilliesBy(int userid);

    void addFamilly(int userid, Familly familly);

    Familly findFamillyById(int famillyid);

}

인터페이스에 추가해주고,,, 실제로 하는 일은 대부분이 도메인 또는 dao로 위임하는 것입니다.

@Service
@Transactional
public class UserServiceImpl extends
        GenericServiceImpl<User, UserParams, UserDao> implements
        UserService {

    public Set<Familly> findFamilliesBy(int userid) {
        return get(userid).getFamillies();
    }

    public void addFamilly(int userid, Familly familly) {
        get(userid).addFamully(familly);
    }

    public Familly findFamillyById(int famillyid) {
        return dao.fingFamillyById(famillyid);
    }

}

그럼 도메인에서 할 일은 도메인에서 처리를 하고..

    public Set<Familly> getFamillies() {
        if (famillies == null)
            famillies = new HashSet<Familly>();
        return famillies;
    }

    public void addFamully(Familly familly) {
        getFamillies().add(familly);
    }

DAO에서 할일은 DAO에서 처리합니다,

    public Familly fingFamillyById(int famillyid) {
        return (Familly) getSession().createQuery(
                “from Familly where id = ” + famillyid).uniqueResult();
    }

어디서 처리하든 tx facade 역할을 하고 있는 service를 거쳐왔기 때문에, 트랜잭션 처리가 됩니다.

ps: 스프링 @MVC 혹은 OSAF의 버그(?) 발견..

    @RequestMapping(value=”/base/user/famillyadd.do”, method=RequestMethod.GET)
    public void famillyadd(ModelMap model){
        model.addAttribute(“model”, new Familly());
    }

여기서 @RequestMapping의 value를 주지 않아도(CoC로 인해 같은 결과가 나와야 함) 똑같이 동작해야 하는데, 생략할 경우, seach.do를 실행할 때(위 메서드와는 전혀 관계 없는 OSAF GenericController의 update() 메서드 시그너쳐 int id를 Integer로 바꾸라는 에러가 발생함.. @_@)

[DDD] Whiteship’s DDD 아키텍처

기대는 하지 마시구요. @_@ 기존 틀(빈약한 도메인 객체 + 두꺼운 서비스 계층)에서 벗어난다는게 이렇게 머리 아픈 일인지 몰랐습니다. DDD 공부라도 제대로 했었어야 하는데.. 역시 개념없는 코드와 코드 없는 개념은 아무것도 아닌 것 같아요. 힘 없는 정의와 정의없는 힘처럼… 각설하고.. 오늘 구현해볼 아키텍처를 그려봤습니다.

일단 Web 계층에서는 Application 계층에 있는 Thin Facade 내지 서비스를 호출할 겁니다.

Thin Facade는 도메인 객체로 대부분의 역할을 위임할 겁니다.

도메인 계층에서는 Thin Facade로 부터 어떤 일을 위임 받은 도메인 객체는 비즈니스 로직을 담당할 것이고
– 컬렉션에 저장, 조회 등의 작업을 할 때는 계층에 위치한 Repository를 이용할 겁니다. 여기서 Repository는 DAO와는 개념이 다르다(참조: http://aeternum.egloos.com/1160846)는 것에 주의해 주세요.
– Repository는 모든 Entity가 아닌, Entry Point에 해당하는 Entity에 대한 것만 만들 겁니다.
– Email, JMS 등의 기능이 필요하다면 Infrastructure에서 제공하는 서비스를 이용할 겁니다.

시나리오 1: User


UserController가 요청을 받으면 UserFacade가 이 요청을 받아서 User의 특정 메서드를 호출하고, User는 UserRepository를 사용하여 원하는 작업을 한다.

시나리오 2: User->Address


위 상황에서 Value 타입인 Address가 추가 됐을 때 모습으로 별반 달라진 건 없음. Value 타입이 Entity 타입과 어떤 차이가 있는지 보여주는 시나리오.

시나리오 3: User->Address, Order


Order라는 새로운 Entity이자 Entry Type이 추가되자 Controller, Facde, Repository, Dao가 추가된다. User와 Order는 양방향 관계로 서로가 서로를 참조한다. 비즈니스 요구사항에 따라 방향성은 달라질 수 있다. 또한 현재는 각자가 자신의 Repository만 참조하고 있는데, 이 모습도 비즈니스 요구사항에 따라 달라질 수 있다. 중요한 건, 비즈니스 요구사항에 따라 도메인 객체만 변경하면 될 뿐, 나머진 그대로라는 것이다.

주의 할 것은 전체 아키텍처에서도 그렸듯이 도메인 계층에서 Application 계층을 참조하지 않는다는 것이다. Application 계층을 참조할 일이 있다는 것은 도메인 계층이 다시 Application 계층으로 일을 넘긴다는 것인데, 그렇게 되면 Thin Facade라는 가정이 어긋나게 되고 비즈니스 로직이 Application 계층쪽으로 새어나가게 될 것이다.

시나리오 4: User->Address, Order->OrderLine


이번에는 새로운 Entity OrderLine을 추가했다. 하지만 이 Entity는 Entry Point가 아니라 Order에 종속된 생명주기를 지나고 있다. 따라서 Order가 OrderLine까지 같이 관리하며 OrdeLineRepository는 만들지 않는다. 대신 Infrastructure에 OrderLineDao를 추가하여 OderRepository가 OrderLine을 DB에 넣을 때 이용할 수 있게 도와준다.

이제 구현해보는 일만 남았군요. 캬~~ 코딩하기 전에 그림 그려보는 것도 좀 도움이 되네요. 코딩하면서 계속 디자인을 바꾸니깐.. 테스트고 뭐고.. @_@ 일단 저렇게 큰그림 잡고 조금씩 테스트 해 가면서 기왕이면 TDD로 ㄱㄱ 해보렵니다! 파이팅!! 오늘은 이거 못 만들면 퇴근 못하니깐…ㄷㄷㄷㄷㄷ… 내일 스터디는 밤새고 가야할지도…??? 헤헷 후딱 하고 퇴근해버려야지~

[DDD] DDD 입문에 좋은 글

여기에 왕창있더군요. 사부님이 읽고 공부하라고 적극 추천해주는 바람에 출근해서 지금까지 모두 정독해버렸습니다. 개념 설명도 충분하고 중간 중간 코드도 같이 주셔서 이해하기가 더 좋았습니다. TDD까지 권하시는 센스.. 캬~~ 읽으면서 많이 감탄했네요.

이터니티님 감사합니다. 잘 읽었습니다~ 계속해서 연재해 주세요~

2009/03/25   Domain-Driven Design의 적용-4.ORM과 투명한 영속성 4부 [1]
2009/02/27   Domain-Driven Design의 적용-4.ORM과 투명한 영속성 3부
2009/02/23   Domain-Driven Design의 적용-4.ORM과 투명한 영속성 2부
2009/02/15   Domain-Driven Design의 적용-4.ORM과 투명한 영속성 1부
2009/01/18   Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 7부 [2]
2009/01/02   Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 6부
2008/12/24   Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 5부 [2]
2008/12/17   Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 4부
2008/12/13   Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 3부
2008/12/09   Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 2부
2008/12/05   Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 1부
2008/11/30   Domain-Driven Design의 적용-2.AGGREGATE와 REPOSITORY 5부
2008/11/27   Domain-Driven Design의 적용-2.AGGREGATE와 REPOSITORY 4부
2008/11/25   Domain-Driven Design의 적용-2.AGGREGATE와 REPOSITORY 3부
2008/11/23   Domain-Driven Design의 적용-2.AGGREGATE와 REPOSITORY 2부
2008/11/20   Domain-Driven Design의 적용-2.AGGREGATE와 REPOSITORY 1부
2008/11/17   Domain-Driven Design의 적용-1.VALUE OBJECT와 REFERENCE OBJECT 4부
2008/11/17   Domain-Driven Design의 적용-1.VALUE OBJECT와 REFERENCE OBJECT 3부
2008/11/16   Domain-Driven Design의 적용-1.VALUE OBJECT와 REFERENCE OBJECT 2부
2008/11/15   Domain-Driven Design의 적용-1.VALUE OBJECT와 REFERENCE OBJECT 1부 [2]

파트 3: Refactoring Toward Deeper Insight

리팩터링 수준

다른 책들은 너무 로우 레벨로 얘기하는데 그 보다는 도메인에 대한 통찰을 통한 또는 모델이 말하고자 하는 바를 분명하게 하는 것이 시스템의 실용성에 더 지대한 영향을 주는 리팩터링 이라는 이야기..

The refactorings that have the greatest impact on the viability of the system are those motivated by new insights into the domain or those that clarify the model’s expression through the code.

Deep Model

모델을 좀 더 심도있게 바라보도록.. 피상적인 모습으로만 축출한 모델이 다가 아니라는거…

A deep model provides a lucid expression of the primary concerns of the domain experts and their most relevant knowledge while it sloughs off the superficial aspects of the domain.

Supple Design

변경을 반영할 수 있는 설계가 되어야 한다.

In a process of constant refactoring, the design itself needs to support change.

Deep Model과 Supple Design은 Model-Driven Design을 뒷받침하는 두 개의 축이다.

A MODEL-DRIVEN DESIGN stands on two legs. A deep model makes possible an expressive design. At the same time, a design can actually feed insight into the model discovery process when it has the flexibility to let a developer experiment and the clarity to show a developer what is happening.

The Discovery Process

좋은 모델을 찾아가는 과정은 개인의 창의성에 달려있기도 하지만, 여러 패턴을 따를 수도 있겠다.

You will usually depend on creativity and trial and error to find good ways to model the concepts you discover, but sometimes someone has laid down a pattern you can follow.

가끔은 이론적인 얘기로 머리를 환기시키는 것도 좋네요.

Factories

Entity와 Aggregate는 보통 생성자에서 만들기는 너무 복잡해질 정도로 커지는 경향이 있다.

보통 객체를 요구하는 쪽에서 여러 인자들과 함께 생성자를 호출한다. 이러면 객체를 생성할 때 너무 많은 정보들을 알게 된다. 즉, 객체에 대한 클라이언트들이 객체 생성에 관한 정보를 알게 되는 것이고, 이 것은 도메인 객체와 Aggregate에 대한 Encapsulation을 깨트린다.

실제에서도 플라스틱, 고무, 철, 실리콘 등으로 프린터를 직접 만들진 않는다. 이런 복잡한 것들을 포함시키면 이해하기 어려운 설계가 된다. 따라서 복잡한 객체 생성 과정을 캡슐화하는 새로운 개념이 필요하다. 그게 바로 Factory다.

객체 생성 과정을 원자화Atomic하는 것은 중요하다. 그렇지 않으면, 만들다 만 객체를 생성할 수도 있다. 특히 Aggregate의 경우에 그렇다. 객체 생성이 실패하면, 예외를 발생시켜서 잘못된 값이 전달되지 않도록 해야한다.

따라서 복잡한 객체나 Aggregate 객체를 생성하는 책임을 별도의 객체로 위임하는 것이 좋다. 인터페이스를 제공하여 클라이언트가 구현 클래스에 직접 접근할 필요가 없도록 하라.

Factory와 관련된 GoF의 Design Pattern 중에 Factory Method 패턴과 Abstract Factory 패턴이 있다. 디자인 관점이 아니라 도메인 모델링 관점에서 살펴보겠다.

사용자 삽입 이미지참조 : http://en.wikipedia.org/wiki/Factory_method_pattern

Factory Method는 다른 객체를 생성하는데 필요로 하는 정보를 가지고 숨겨둔 객체 메소드이다. 이것은 클라이언트가 Aggregate에 속한 객체를 얻고 싶을 때 유용하다. Aggregate 루트에 객체 생성을 책임지는 메소드를 추가하면 된다.

사용자 삽입 이미지참조 : http://www.dofactory.com/Patterns/PatternAbstract.aspx

Abstract Factory는 객체 생성이 더 복잡하거나 객체 생성이 여러 개의 객체 생성을 포함하고 있는 경우에 사용한다. 예를 들어, Aggregate를 생성할 때, 그에 대한 책임을 별도의 Factory 객체에게 위임할 수 있다. Factory를 만드는 것은 객체 캡슐화를 위배하기 때문에 주의깊게 사용해야 한다.

Entity Factory와 Value Object Factory의 차이점. Value Object는 변하지 않는immutable 객체기 때문에, 한 번 만들어 지면 변하지 않아야 한다. Entity 는 immutable하지 않다. 매번 변하며, 식별성identity을 가지고 있다.

다음의 경우 Factory보다는 생성자를 사용하는게 좋다.

  • 객체 생성이 복잡하지 않을 때.
  • 객체 생성이 다른 객체 생성 과정을 포함하고 있지않으며, 오로지 인자로 넘어온 값들만 필요로 할 때.
  • 클라이언트가 구현 과정에 관심이 있을 때.
  • 계층구조가 없는 단순한 타입일 때. 어떤 구현체를 제공해야 할지 선택할 필요가 없기 때문에..

Entity 객체를 가져올 때 주의해야 할 것은 DB의 identity와 객체의 identity를 맞춰주어야 한다는 것이다.