작업 단위

비즈니스 트랜잭션의 영향을 받은 객체의 리스트를 유지 관리하고, 변경 내용을 기록하는 일과 동시성 문제를 해결하는 일을 조율한다.

작업의 일관성 유지를 위함

example 단위작업 기본 구현

class UnitOfWork {
    private List newObjects = new ArrayList();
    private List dirtyObjects = new ArrayLis();
    private List removedObjects = new ArrayList();

    // 추가된 객체
    public void registerNew(DomainObject obj) {
        Assert.notNull("id not null", obj.getId());
        Assert.isTrue("object not dirty", !dirtyObjects.contains(obj));<kbd></kbd>
        Assert.isTrue("object not removed", !removedObjects.contains(obj));
        Assert.isTrue("object not already registered new", !newObjects.contains(obj));
        newObjects.add(obj);
    }

    // 수정된 객체
    public void registerDirty(DomainObject obj) {
        Assert.notNull("id not null", obj.getId());
        Assert.isTrue("boject not removed", !removedObjects.contains(obj));
        if (!dirtyObjects.contains(obj) && !newObjects.contains(obj)) {
            dirtyObjects.add(obj);
        }
    }

    // 삭제된 객체
    public void registerRemoved(DomainObject obj) {
        Assert.notNull("id not null", obj.getId());
        if (newObjects.remove(obj)) return;
        dirtyObjects.remove(obj);
        if (!removedObjects.contains(obj)) {
            removedObjects.add(obj);
        }
    }

    // 조회된 객체
    public void registerClean(DomainObject ob) {
        AssertnotNull("id not null", obj.getId());
    }

    // 커밋
    public void commit() {
        insertNew();
        updateDirty();
        deleteRemoved();
    }

    private void insertNew() {
        for (Iterator objects = newObjects.Iterator(); objects.hasNext(); ) {
            DomainObject obj = (DomainObject) objects.next();
            MapperRegistry.getMapper(obj.getClass)).insert(obj);
        }
    }
}

example 단위작업 조회 설정 구현(TheadLocal)

class UnitOfWork {
    ...
    private static ThreadLocal current = new ThreadLocal();

    public static void newCurrent() {
        setCurrent(new UnitOfWork());
    }

    public static void setCurrent(UnitOfWork uow) {
        current.set(uow);
    }

    public static UnitOfWork getCurrent() {
        return (UnitOfWork) current.get();
    }
}

example DomainObject

class DomainObject {
    protected void markNew() {
        UnitOfWork.getCurrent().registerNew(this);
    }

    protected void markClean() {
        UnitOfWork.getCurrent().registerClean(this);
    }

    protected void markDirty() {
        UnitOfWork.getCurrent().registerDirty(this);
    }

    protected void markRemoved() {
        UnitOfWork.getCurrent().registereRemoved(this);
    }
}

example Album

class Album extends DomainObject {
    public static Album create(String name) {
        Album obj = new Album(IdGenerator.nextId(), name);
        obj.markNew();
        return obj;
    }

    public void setTitle(String title) {
        this.title = title;
        markDirty();
    }
}

example EditAlbumScript

class EditAlbumScript {
    public static void updateTitle(Long albumId, String title) {
        // 1. 작업단위 생성
        UnitOfWork.newCurrent();
        // 2. 작업 진행 : 수정을 통한 dirty 상태
        Mapper mapper = MapperRegistry.getMapper(Album.class);
        Album album = (Album) mapper.find(albumId);
        album.setTitle(title);
        // 3. 작업 완료 : 실질적 update 실행
        UnitOfWork.getCurrent().commit();
    }
}

서비스 계층에서 작업단위 핸들링

example UnitOfWorkServlet

class UnitOfWorkServlet {
    final protected void doGet(HttpServletRequest request
            HttpservletResponse response) throws ServletException, IOException {
        try {
            UnitOfWork.newCurrent();
            handleGet(request, response);
            UnitOfWork.getCurrent().commit();
        } finally {
            UnitOfWork.setCurrent(null);
        }
    }

    abstract void handleGet(HttpServletRequest request,
            HttpservletResponse response) throws ServletException, IOException;
}

프레젠테이션 계층에서 작업단위 핸들링

식별자 맵

모든 객체를 한 맵에 로드해 각 객체가 한 번씩만 로드되게 한다. 객체를 참조할 때는 맵을 이용해 객체를 조회한다.

고려사항

  1. 키의 선택
    1. 일반적으로 테이블의 기본식별자(PK)
  2. 명시적 또는 범용적
    1. 명시적 : findPerson(1)
    2. 범용적 : find("Person", 1)
  3. 식별자 맵의 수
    1. 클래스 당 하나 : 명시적
    2. 전체 세션에 하나 : 범용적
  4. 식별자 맵의 위치
    1. 작업 단위 위치
    2. 세션과 연결된 레지스트리
    3. ThreadLocal 추천 : 쓰레드에 안전해서 동기화 처리가 필요없음

명시적 식별자(클래스 당 하나)를 추천

값객체(Value Object)에는 식별자 맵을 사용할 필요가 없다.

식별자 맵의 장점

  • 캐시를 통한 성능
  • 엔티티에 == 가능

지연 로드

필요한 데이터를 모두 포함하지는 않지만 데이터가 필요할 때 가져오는 방법을 아는 객체

구현방법

  1. 지연 초기화 : null 비교
  2. 가상 프록시 : Proxy
  3. 값 홀더 : Wrapper
  4. 고스트 : 초기에는 비어있는(null아님)객체이나, 조회 시 실제 DB 접근

테이블 데이터 게이트웨이

데이터베이스 테이블에 대한 게이트웨이의 역할을 하는 객체. 한 인스턴스가 테이블의 모든 행을 처리한다.

테이블 데이터 게이트웨이

테이블 데이터 게이트웨이

그림 : 데이터 집합 기반 게이트웨이와 지원 데이터 홀더의 클래스 다이어그램

행 데이터 게이트웨이

데이터 원본의 단일 레코드에 대한 게이트웨이 역할을 하는 객체. 행마다 인스턴스 하나가 사용된다.

행 데이터 게이트웨이

활성 레코드와 차이는 도메인 논리가 포함되어 있으면 활설레코드이며, 없으면 행 데이터 게이트웨이이다.

활성 레코드

데이터베이스 테이블이나 뷰의 행을 래핑하고, 데이터베이스 접근을 캡슐화하며, 해당 데이터에 대한 도메인 논리를 추가하는 객체

합성 레코드

테이블 모듈에 어울림

풍부한 객체지향적 도메인 모델(상속, 확장 등)에는 어울리지 않는다. 데이터 매퍼가 어울림

데이터 매퍼

객체와 데이터베이스 사이에서 둘 사이는 물론 매퍼 자체에 대한 독립성을 유지하면서 데이터를 옮기는 매퍼의 한 계층

데이터 매퍼

  • 식별자 맵, 레지스트리
  • 분리 인터페이스
  • 풍부한 생성자
  • 메타데이터 매핑

도메인 모델에 어울림

트랜잭션 스크립트

비즈니스 논리를 프로시저별 구성해 각 프로시저가 프레젠테이션의 단일 요청을 처리하게 한다.

네이밍의 이유는 대부분의 경우 데이터베이스 트랜젝션 마다 트랜젝션 스크립트 하나가 있기 때문

  • 단순함
  • 복잡해지면 좋은 설계 상태 유지가 힘듬
  • 코드 중복

대부분 프로젝트 패키지 구조는 트랜잭션 스크립트에 최적화 되어 있는 거 같다 : 데이타와 행위가 패키지로 분리됨

닭 잡을 때(단순 논리)는 닭 잡는 칼(트랜젝션 스크립트)로 충분한다.

도메인 모델

동작과 데이터를 모두 포함하는 도메인의 객체 모델

도메인 모델은 각 객체가 하나의 기업과 같이 복잡하거나 주문서의 내용 한 줄과 같이 간단한, 의미 있는 하나의 대상을 나타내는 상호 연결된 객체의 연결망으로 이뤄진다.

이러한 데이터와 프로세스는 프로세스와 작업 대상 데이터를 가깝게 배치하기 위한 클러스터를 형성한다 동시에 캡슐화까지 연관지어서 생각해 볼 수 있다.

  • 패키지
  • 모듈
  • 라이브러리

단순 도메인 모델 (Anemic domain model) 보다는 풍족한 도메인 모델(Rich domain model)을 사용하도록 하자.

Fat Service(트랜잭션 스크립트에 어울림)보다는 Thin Service(도메인 모델에 어울림)를 지향하자. 서비스는 결국 파사드, 또는 위임자일 뿐이다. 풍부해야할 부분은 도메인 모델이어야 한다.

EJB는 이미 망했다는 것을 증명했다.

  • 이는 그저 벤더(상용 EJB 컨테이너를 통한)들의 돈벌이 수단이었을 뿐이다.
  • POJO는 새로운 대안을 제시했으며, 현재의 상황은(Spring) 이를 성공적으로 증명했다.

“패러다임의 전환”

행위와 데이터를 분리하는 절차적 사고 방식에서 연관된 행위와 데이터를 응집도 있게 한 객체(모델)로 생각하는 객체지향적 사고 방식 전환이 필요하다.

본인은 현재 범용적으로 퍼진 패키지 구조가 이러한 사고방식을 저해하는 큰 요인 중 하나라고 본다.

DataSource Layer에서 데이터 매퍼와 잘 어울린다.

테이블 모듈

데이터베이스 테이블이나 뷰의 모든 행에 대한 비즈니스 논리르 처리하는 단일 인스턴스 DB에 어느정도 의존성이 있다.

도메인 모델과 가장 큰 차이점은 주문이 여러개인 경우 도메인 모델은 주문 수 만큼 객체를 사용하지만, 테이블 모듈은 모든 주물을 객체 하나가 처리한다는 것이다. 즉, 식별자가 존재하지 않는다.

DataSource Layer에서 레코드 집합과 잘 어울린다.

서비스 계층 (Service Layer)

구분 도메인 모델 트랜잭션 스크립트
구현의 변형 도메인 파사드(Domain facade) 작업 스크립트(Operation script)
호출에 대한 고려 Thin service Fat service
작업 식별 유스케이스 모델 사용자 인터페이스

도메인 로직 패턴

  • 트랜잭션 스크립트(Transaction script)
  • 테이블 모듈(Table module)
    • RecordSet을 이용하여 다양한 테이블을 투영할 수 있다.
  • 도메인 모델(Domain model)

데이터 원본 계층

  • 트랜잭션 스크립트
    • 행 데이터 게이트웨이
    • 테이블 데이터 게이트웨이
    • 낙관적 오프라인 잠금
  • 테이블 모듈
    • 레코드집합(RecordSet)
    • 테이블 데이터 게이트웨이
  • 도메인 모델
    • 활성 레코드(Active Record)
    • 데이터 매퍼

프레젠테이션 계층

  • MVC
    • 컨트롤러
      • 페이지 컨트롤러
      • 프런트 컨트롤러
      • 템플릿 뷰 : 서버페이지
      • 변환 뷰 : XSLT
      • 2단계 뷰 : Layout
  • 하위계층
    • 원격파사드(Service?) + 데이터 전송 객체(DTO)

Unknown

1부 4장 동시성

고립성(isolation)쪽 레벨이 정확하게 이해가 안됨 : https://support.microsoft.com/ko-kr/kb/601430 로 이해가 됨

개인적으로 많이 사용하는 단축키를 정리해 봅니다.

해당 포스트는 지속적으로 업데이트됩니다. OS-X 10.5 이상 기준

기호설명 도움말

  • : command
  • : control
  • : shift
  • : option(alt)
  • : esc
  • : return(enter)
  • : tab

파일

  • ⌘O : 클래스 찾기 - 단어사이 대문자로 조회하면 더 이득
  • ⌘⇧O : 파일 찾기 - 유사 단축키로 ⇧⇧ 도 많이 사용함
  • ⌘E : 최근 사용 파일 찾기
  • ⌃⇥ : 최근 파일로 이동 - 위 ⌘E 와 유사하나 UX가 약간 다름

Layout

  • ⌘1 : Project view
  • ⌘3 : Find view
  • ⌘4 : Run view
  • ⌘8 : Hierarchy - ⌃⌥H 로 메소드 호출을 찾으면 노출됨
  • ⌘9 : Version Control - git, task 관리
  • ⇧⎋ : 활성화된(현재 선택된) Layout 닫기

Refector - 강추

  • ⌘⌥M : 메서드 분리
  • ⌘⌥C : 상수 분리 생성 (public static final ...)
  • ⌘⌥F : 필드(객체변수) 분리
  • ⌘⌥V : 지역변수 분리
  • ⇧ F6 : rename
  • ⌘ F6 : 메서드 시그니쳐(명칭, 반환타입, 파라메터타입과 명칭, 접근자 등) 변경

에디터

  • ⌥⏎ : Quick fix - 오류 해결 솔루션 제시 강추
  • ⌘⇧↑, ⌘⇧↓ : 선택된 코드 영역 위, 아래로 이동
  • ⌘ Del : 한 줄 삭제
  • ⌘⇧] : 에디터 탭 기준 오른쪽으로 이동
  • ⌘⇧[ : 에디터 탭 기준 왼쪽으로 이동
  • ⌥↑ : 커서 기준으로 단위 영역(단어, 영역, 문장, 메서드 등) 선택 - 강추
  • ⌘ F12 : 현재 클래스 필드, 메서드 등 목록 노출 - lombok 사용할 시 편함
  • ⌘N : 각종 코드 생성 (getter, setter, override, DI 등)
  • ⌃O : 상위클래스 override
  • ⌃⌥O : Auto import
  • ⌥⇥ : 스플릿(화면분할) 상태에서 화면 간 이동
    • Split Vertical 단축키를 지정하고 쓰면 더 좋음 - 저는 ⌃⌥V로 지정
  • ⌥ F1 then 1 or : 현재파일 Project View 에서 활성화

실행

  • ⌘ F9 : 컴파일
  • ⌘R : Run View에서 현재 포커싱 실행 (실행 중이면 재실행)
  • ⌘ F2 : Run View에서 현재 활성화된 실행을 Kill
  • ⌃R : 최근 실행한 것을 다시 실행
  • ⌃⌥R : 현재 커서나 포커싱 된 파일에 실행가능한 것을 노출 후 선택 실행 가능
    • 보통 단위테스트 시 해당 커서가 메서드안에 있는 상태에서 위 단축키를 누르고 2를 누르면 바로 메소드 테스트 실행
  • ⌃D : 최근 실행한 것을 다시 실행 - 디버깅 모드
  • ⌃⌥D : 현재 커서나 포커싱 된 파일에 실행가능한 것을 노출 후 선택 실행 가능 - 디버깅 모드

생성

  • ⌘⇧T : 테스트케이스생성
    • 이후 ⌘N 을 이용하면 테스트메서드 생성이 편리함

이동

  • ⌘B : 클라이언트 코드에서 해당 메소드 선언으로 이동
  • ⌘⌥B : 클라이언트 코드에서 해당 메소드 구현으로 이동 - 강추
  • ⌘U : 구상클래스에서 해당 메소드 선언으로 이동
  • ⌘⌥←, ⌘⌥→ : 이전, 다음 작업영역으로 이동

작업(형상)관리

  • ⌘K : 커밋
    • 커밋 확인 후 ⌃⏎를 치면 됨
    • 만약 review 여부를 물으면 Commit 버튼을 으로 포커싱 후 Space를 입력
  • ⌥⇧N : Task 추가 - ⌘9 와 연계하면 작업관리 시 편함
  • ⌥⇧C : Recent Changes

Multiline

  • ⌘⌃G : 커서가 위치한 단어 기준 multiline 제어
  • ⌘⇧8 + ⇧↑, ⇧↓ : multiline 모드 활성화 후 라인 선택
  • ⌥⇧ MouseLeftClick : 마우스로 클릭한 위치 기준으로 multiline 제어

검색

  • ⌘⇧F : 문자열 전체 검색

바로가기

  • ⌘, : 환경설정
  • ⌘; : 프로젝트 환경설정

Syntax

  • iter : for-each 구문 생성
  • fori : for 구문 생성
  • psfs : public static final String
  • psfi : public static final int
  • psvm : public static void main(String[] args)
  • thr : throw new