[치정] 영역 구동 디자인 정리 - 실체와 값 대상 디자인

솔리드


인용문


분야 구동 디자인에서 실체의 디자인은 통용 언어의 핵심이라고 할 수 있고 모델 구분에서 가장 먼저 고려해야 할 것이다.어떻게 실체를 설계하는 것과 어떻게 경계를 구분하는 상하문이 똑같이 중요하다.실체의 개념은 바로 통용 언어의 완전성을 확보하는 것이다.영역 구동은 디자인 실체의 관심사를 데이터의 속성과 표의 관련에서 부유한 행위의 영역 개념으로 전환시켰다.
실체는 가변성을 가진다. 이것은 값 대상과 비교적 뚜렷한 구분이다. 즉, 실체는 지속적으로 변화하고 수정할 수 있으며 유일한 표지를 가진다.실체를 디자인할 때 크루드의 디자인 사고방식에서 벗어나야 한다.관심의 중점을 데이터 모델 디자인에서 실체 모델로 옮기다.실체는 어떤 개념을 표현하고 어떤 행위를 가지며 영역의 범위가 어떤 것인지를 나타낸다.실체의 유일한 표지는 실체를 구분하는 데 사용되며 실체의 전체 생명 주기에서 이 유일한 표지는 변하지 않는다.

설계 솔리드


솔리드 설계에서 솔리드의 고유한 ID를 먼저 결정해야 합니다.자바의 실체 디자인에서 프레임을 빌려 유일한 표식을 실현할 수 있다.여기서는 구체적인 실현 세부 사항을 먼저 토론하지 않는다.디자인의 유일한 표지는 사실 여러 가지 방식이 있을 수 있다.
  • 사용자가 유일한 표지를 입력하고 프로그램이 입력에 따라 식별 가능한 수치와 기호를 생성하는데 이런 방식은 생성 규칙을 수정하기 불편하고 입력 충돌도 존재한다.
  • 응용 프로그램 생성, 예를 들어 자바가 자체로 가지고 있는 UUID 생성기.apache Commons id 생성 구성 요소입니다.
  • 영구화 메커니즘, DB의 시퀀스 값 Sequence 또는 자체 키 증가.
  • 다른 상하문은 유일한 표지를 제공한다. 예를 들어 로컬 상하문에도 로컬 User가 있고 전역적인 예를 들어 로그인 시스템의 사용자 id를 로컬 User 실체의 메인 키로 한다. 이런 것은 조심스럽게 사용하고 시스템의 자치성을 최대한 확보해야 한다.
  • 위임 표지.많은 상황에서 유일한 표지는 분야 개념과 무관하고 클라이언트도 이 표지에 주목할 필요가 없다.이럴 때 ORM 도구로 처리하고 추상적인 부류를 써서 id 생성을 전문적으로 할 수 있다.다른 하위 클래스의 실체는 자신의 영역의 모델과 행위를 즉각적으로 주목하기만 하면 된다.구체적인 실현은 Hibernate나 Jpa 같은 도구를 사용할 수 있다.다음은 jpa로 자체 키 증가를 생성하는 부류입니다.
  • package com.lijingyao.bookrent.entity;
    
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.MappedSuperclass;
    
    /** * Created by lijingyao on 15/12/20 13:14. */
    @MappedSuperclass
    public abstract class LayerSuperType {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
    
        protected Long getId() {
            return id;
        }
    
        protected void setId(Long id) {
            this.id = id;
        }
    }
    

    실체는 id 생성에 관심을 두지 않아도 된다. 아래와 같이 사용자를 대표하는 실체이다.
    package com.lijingyao.bookrent.entity;
    
    import com.sun.istack.internal.NotNull;
    
    import java.util.Calendar;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.Index;
    import javax.persistence.Table;
    
    /** * Created by lijingyao on 15/12/20 12:48. */
    @Entity
    @Table(
            name = "br_user",
            indexes = {@Index(name = "IDX_USER_NAME", columnList = "name", unique = false)})
    public class User extends LayerSuperType {
    
        @NotNull
        @Column(name = "name")
        private String name;
    
    
        @Column(name = "utc_create")
        private Calendar utcCreate;
    
        @Column(name = "utc_modified")
        private Calendar utcModified;
    
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Calendar getUtcCreate() {
            return utcCreate;
        }
    
        public void setUtcCreate(Calendar utcCreate) {
            this.utcCreate = utcCreate;
        }
    
        public Calendar getUtcModified() {
            return utcModified;
        }
    
        public void setUtcModified(Calendar utcModified) {
            this.utcModified = utcModified;
        }
    }
    

    솔리드 정의


    실체를 정의하려면 먼저 영역의 통용 언어를 이해해야 한다.실체는 완전한 통용 언어를 표현하는 기초 위에서 실체의 속성을 정의한 다음에 실체의 유일한 표식도 정의해야 하기 때문이다.실체를 정의할 때 어떤 행위가 이 실체에 속하고 어떤 직책이 본 실체가 갖춰야 하는지를 분명히 해야 한다.실체의 속성 검증 과정에서도 실체로 돌아가야 한다.검증을 호출하는 과정도 반드시 지속화의 단계에 이르러서야 진행되는 것은 아니다. 선행 검증과 같이 검증 행위를 실체로 회귀시킬 수 있다.현재 일부 유행하는 개원 프레임워크도 여러 가지 실체 검증 방식을 지원한다. 예를 들어 JPA의 주석으로 검증할 수 있다. 이런 것은 지연 검증에 속하지만 실체의 본질과 행위로 돌아가면 검증 자체도 실체 행위의 일환이다.

    값 객체


    개념 및 특성


    값 대상, 값이 변하지 않는 대상, 즉 특정한 의미를 가진 표현이다.그래서 값 대상은 유일한 표식이 없고 통용 언어를 반영하는 방식으로서 영역 구동의 한 부품과 같다.값 대상은 실체 개념에 비해 더욱 간단하지만 관건은 하나의 영역 개념이 실체로 설계되는지 값 대상으로 설계되는지이다.디자인 선택을 할 때 값 대상의 특징을 비교할 수 있다. 1.값 대상은 영역의 하나를 도량하거나 묘사하는 데 쓰인다.2. 불변량(불변성)으로 할 수 있다.서로 다른 관련 속성을 하나의 개념 전체로 조합하다.다른 값 대상과 같은 비교를 할 수 있다(속성이 같은 것은 같은 대상이다).협업 대상에 부작용(부작용 없음)을 일으키지 않는다. 사실 값 대상의 개념을 정리할 때 실체적으로 많은 값 대상을 설계하는데 자바에서 가장 흔히 볼 수 있는 것은 매거진으로 표시하고 실현하는 것이다.매거된 불변성은 많은 개념을 쉽게 표현할 수 있다.값 대상의 디자인은 통용 언어를 내집적으로 표현하기 위한 개념이다.영역 모델링을 할 때 유일한 표지가 필요 없는 개념에 대해 값 대상으로 설계할 수 있다.예를 들어 우리는 책을 빌리는 것과 함께 지금 이런 개념을 묘사하고 있다. 하나의 실체인'책'에 대해 만약에 이런 개념인'문학책'이 있다면 문학책은 최대 1개월만 빌릴 수 있고 과학기술류의 책은 최대 10일까지 빌릴 수 있다.이곳의 문학책은 변하지 않는 개념이므로 책의 분류에 대해 하나의 값 대상으로 설계할 수 있다.값 대상의 관리와 생성은 공장 방법이나 Builder의 디자인 모델을 가장 잘 활용한다.구조 함수는 좋은 값 대상을 초기화하는 것이 개념의 전체성에 유리하다.다섯 번째 부작용이 없는 행위에 대해 자바8의 lambda 표현식과 결합하거나 함수식 프로그래밍을 결합하면 더욱 잘 이해할 수 있다. 함수식 프로그래밍은 부작용이 없는 것이다. 왜냐하면 대상 내부의 상태를 바꾸지 않기 때문이다. 마찬가지로 값 대상도 다른 실체의 상태를 바꾸지 않고 단지 하나의 개념을 출력하는 데 사용되기 때문이다.

    컨텍스트에서 통합


    집적치 대상은 집적과 최소 직책을 최소화해야 한다.만약 값 대상이 상위 상하 문장의 집합에 의존해야 한다면.가령 방금 책을 빌린 장면이라고 가정하면 서적 관리 시스템은 하나의 상하문에서 책을 임대하는 상하문에서 다음과 같은 개념을 표시해야 한다. 서적 유형(bookType)+임대기록(record)은'XX류 Top10 임대 서적'을 정의할 수 있다.이 개념은 하나의 값 대상인 BestRent로 설계할 수 있다.Book의 type 속성은 여기에 있는 BestRent에 포함되지 않습니다.책의 유형을 책타입이라는 자신이 가진 속성을 통해 나타낸다.다음은 간단한 값 객체입니다.
    package com.lijingyao.bookrent.vo;
    
    /** * Created by lijingyao on 15/12/26 15:00. */
    public class BestRent {
    
        /** * The type of a book.category see {@link BookType} */
        private String bookType;
    
        /** * the rent number of one book. */
        private Integer topNum;
    
        public BestRent() {
        }
    
        public BestRent(String bookType, Integer topNum) {
            this.bookType = bookType;
            this.topNum = topNum;
        }
    
        public BestRent topNScience(Integer topNum) {
            if (null == topNum) {
                throw new IllegalArgumentException("topNum may not be null.");
            }
            return new BestRent(BookType.SCIENCE.name(), topNum);
        }
    
        public BestRent top10Science() {
            return new BestRent(BookType.SCIENCE.name(), 10);
        }
    
        public void setBookType(String bookType) {
            this.bookType = bookType;
        }
    
        public void setTopNum(Integer topNum) {
            this.topNum = topNum;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
    
            BestRent bestRent = (BestRent) o;
    
            if (bookType != null ? !bookType.equals(bestRent.bookType) : bestRent.bookType != null) return false;
            return !(topNum != null ? !topNum.equals(bestRent.topNum) : bestRent.topNum != null);
    
        }
    
        @Override
        public int hashCode() {
            int result = bookType != null ? bookType.hashCode() : 0;
            result = 31 * result + (topNum != null ? topNum.hashCode() : 0);
            return result;
        }
    }
    

    값 대상을 통해 북 분야의 업무와 북 타입을 주목할 필요가 없다.이 값의 대상은 부작용이 없다. 왜냐하면 어떤 영역의 대상의 상태도 바꾸지 않고 유일한 표식도 필요하지 않기 때문이다.고정된name과고정된bookType은 변하지 않는 개념을 정의할 수 있다. 예를 들어'자연과학류의 Top10'은 top10Science 방법을 통해 얻을 수 있다.또한 값 대상이 equals와hashCode 방법을 실현하는 것이 가장 좋다는 것을 주의해야 한다.한 그룹의 개념이 묘사한 값 대상에 대해 말하자면 완전한 개념을 표현하는 속성 값만 같으면 값 대상은 같다.이것은 top10Science와 topNScience(10)가 같은 대상을 되돌려야 한다는 것을 보장할 수 있다.이것이 바로 값 대상의 불변성이다.

    표준 유형(Standard Type)


    표준 유형은 어떤 사물을 표시하는 묘사 대상의 표현 방식이다.예를 들어 화폐 유형을 나타내는데 표준 유형은 RMB, JPY, AID, USD 등 화폐 유형을 사용할 수 있다.시스템에서 표준 유형을 구축하면 맞춤법 오류를 방지하거나 비표준적인 묘사 대상(예를 들어 임시적인 String 대상)으로 표준 개념을 표시할 수 있다.표준 유형에 대해 개념을 정의하는 상하문에서 실체를 모델링할 수 있고 소비자 측에서 값 대상을 모델링할 수 있다.만약 소비자의 상하문이 묘사 유형 대상의 생명 주기를 유지할 필요가 없다면 이를 값 대상으로 모델링할 수 있다.또는 유지보수를 편리하게 하기 위해 표준 유형을 단독 경계 상하문에 넣는다.소비자는 표준 유형에서 자신이 필요로 하는 속성만 주목하면 된다.집적을 최소화하기 위해서다.유한 집합의 표준 유형은 매거류로 모델링할 수 있다.다음에 전략 모델을 결합시켜 값 대상을 유지하는 행위를 소개할 것이다.

    전략 모드 실현 값 대상 설계


    하나의 값 대상을 구축할 때 불변성의 개념을 고려하여 대상 자체의 Setters 방법을 숨길 수 있어야 한다.구조 함수를 통해서만 위임된 속성을 사용하여 설정할 수 있습니다.현재 위에서 기술한 서적 유형에 대해 값 대상 설계를 고려하고 있다.이곳의 책 유형은 유한한 집합의 값 대상이기 때문에 우리는 자바의 Enum으로 설계할 수 있다.여기서 간단하게 말하자면 정책 모델을 사용하는 것은 서로 다른 값 대상, 또는 유한한 집합의 값 대상이 특정한 고정된 행위 조작(예를 들어 본 예에서 TopN을 얻는 광고어)에 대해 서로 다른 실현을 가지고 있기 때문이다.그리고 자바8의 lambda 표현식을 결합하면 우리의StrategyHandler를 더욱 잘 정의할 수 있습니다.값 대상의 다른handler는 서로 다른 Function으로 정의하여 코드 실현을 간소화할 수 있다.여기에는 도서 광고어를 얻는 RESTful 인터페이스가 있다고 가정합니다.리소스의 Controller는 다음과 같습니다.
    package com.lijingyao.bookrent.controllers;
    
    import com.lijingyao.bookrent.service.RentService;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    
    @RestController
    @RequestMapping("/books")
    public class BookResource {
    
        @Autowired
        private RentService rentService;
    
        @RequestMapping(value = "/{type}/adv", method = {RequestMethod.GET})
        public ResponseEntity getBestSellBookAdv(@PathVariable("type") String type, @RequestParam("num") Integer num) {
            return new ResponseEntity(rentService.getBookAdvByType(type, num), HttpStatus.OK);
        }
    
    }
    

    controller는 서비스를 통해 자원 데이터를 직접 가져옵니다.만약에 서로 다른 유형의 책이 존재한다면 서비스 내부에 switch가 있어야 한다. 가장 간단하고 거친 방법은 다음과 같다.
    
    public String getBookAdvByType(String type, Integer num) {
            if (type.equals("SCIENCE")) {
                return "   Top :" + num.toString();
            }else if(type.equals("NOVEL")){
                return "   Top :" + num.toString();
            }else{
                ...
            }
            return " .";
        }   
    

    이런 방식은 앞으로 책 유형을 확장할 때 서비스 수정이 필요하다는 실현도 개방폐합의 원칙에 부합되지 않는다는 것을 알 수 있다.매거진을 통해 책 유형을 관리하고 함수식 프로그래밍으로 광고어가 발생하는 행위를 처리한다.그러면 서비스 코드는 다음과 같이 작성됩니다.
       package com.lijingyao.bookrent.service;
    
    import com.lijingyao.bookrent.service.vo.BookType;
    
    import org.springframework.stereotype.Service;
    
    /** * Created by lijingyao on 15/12/26 14:37. */
    @Service
    public class RentService {
    
    
        public String getBookAdvByTypeStrategy(String type, Integer num) {
            BookType bookType = BookType.valueOf(type);
            if (null == bookType) {
                return "There is no advertise of this kind of book.";
            }
            return bookType.bestRentOf(num);
        }
    
    
    }
    

    비헤이비어 bookType.bestRentOf의 실현은 구체적인 전략에 맡겼다.전략의Handler, 즉 열거 클래스는 다음과 같다.
      package com.lijingyao.bookrent.service.vo;
    
    import java.util.function.Function;
    
    /** * Created by lijingyao on 15/12/20 13:43. */
    public enum BookType {
    
    
        NOVEL((topNum) -> BestRentUtils.advOfBestRent(" ", topNum)),
        SCIENCE(((topNum) -> BestRentUtils.advOfBestRent(" ", topNum))),
        TECHNOLOGY(((topNum) -> BestRentUtils.advOfBestRent(" ", topNum)));
    
        private Function<Integer, String> strategy;
    
        public String bestRentOf(Integer topNum) {
            return strategy.apply(topNum);
        }
    
    
        BookType(Function<Integer, String> strategys) {
            this.strategy = strategys;
        }
    
    }
    
    

    Springboot 입력을 시작하려면 다음과 같이 하십시오.http://localhost:8080/books/NOVEL/adv?num=20결과를 볼 수 있다."소설 도서의 베스트셀러 Top: 20.이곳의 처리는 간단한 문자열 결합일 뿐이고 구체적인 상하문에서 값 대상에 대해 더욱 복잡한 계산 논리가 있을 것이다.그러면 모든 단독적이고 공통된 전략을 추출할 수 있고 모든 값 대상은 자신의Strategy 클래스를 가지고 내부의 실현을 봉인할 수 있다.정책을 확장하려면, 하나의 정책을 추가하고, 매거진에 그룹 형식을 추가하면 됩니다.

    결어


    값 대상이든 실체든 디자인이 지속될 때 분야 모델에 따라 데이터 모델을 디자인해야 한다. 데이터 모델에 따라 분야 모델을 디자인하는 것이 아니라 분야 모델을 디자인해야 한다.이것은 DDD의 사고방식이기 때문에 데이터 모델이 영역 모델에서 클라이언트에게 유출되는 것을 최대한 피해야 한다.다음 글은 다음 분야의 서비스와 응용 서비스를 총괄할 것이다.

    좋은 웹페이지 즐겨찾기