[자바스터디] 11. 자바기초(9)

SooYeon Yeon·2021년 8월 16일
0

2021 자바스터디

목록 보기
11/13
post-thumbnail

2021-java-study

2021 자바 스터디 8주차 과제

자바 기초

CQRS

CQRS는 Command and Query Responsibility Segregation의 약자로, 명령과 조희의 책임 분리를 의미한다.

즉, 시스템의 상태를 변경하는 작업과 시스템의 상태를 반환하는 작업의 책임을 분리 하는 것이다.

CQRS의 예시 - API 응용 프로그램

CQRS 적용 전 API

사용자를 등록하는 API 코드

[ResponseType(typeof(UserPresentation))]
public async Task<IHttpActionResult> Post(CreateUser command)
{
    UserPresentation user = await _domainLayer.CreateUserAsync(command);
    return CreatedAtRoute("DefaultApi", new { id = user.Id }, user);
}

http 요청을 통해 CreateUser 명령을 입력받아 도메인 계층에 전달하고 명령이 완료되면 Created 응답을 반환한다.
응답에는 생성된 자원의 위치를 나타내는 location 헤더와 생성된 사용자 엔터티에 대한 본문이 포함된다.
이 동작을 지원하기 위해 도메인 계층의 CreateUserAsync() 메서드는 명령 실행 후 UserPresentation 개체를 반환한다.


도메인 계층의 CreateUserAsync() 메소드 코드

public async Task<UserPresentation> CreateUserAsync(CreateUser command)
{
    int userId = await _repository.InsertAsync(new User
    {
        UserName = command.UserName,
        PasswordHash = _passwordHasher.HashPassword(command.Password)
    });
 
    User user = await _repository.FindAsync(userId);
 
    return new UserPresentation
    {
        Id = user.Id,
        UserName = user.UserName
    };
}

이 메소드는 크게 두가지 작업을 수행한다.
첫번째는 CreateUser 명령에 들어있는 정보를 사용해 새 사용자 엔터티를 생성하는 것다.
두번째는 생성된 사용자 엔터티를 조회해 표현 계층 모델로 변환해 반환하는 것이다.
영속 모델(User) 개체를 그대로 반환하면 PasswordHash 속성이 서비스 외부로 노출되는 보안 문제가 발생한다.

CQRS 적용 후 API

사용자 엔터티 조회 작업을 읽기 모델로 분리하고, API 계층이 응답 본문 작성을 위해 분리된 읽기 모델을 사용하도록 수정하면 UsersDomainModel.CreateUserAsync() 메서드는 더이상 값을 반환할 필요가 없으며 새 엔터티 생성에 대한 책임만 가진다.

public async Task CreateUserAsync(CreateUser command)
{
    await _repository.InsertAsync(new User
    {
        Id = command.UserId,
        UserName = command.UserName,
        PasswordHash = _passwordHasher.HashPassword(command.Password)
    });
}

User 엔터티를 저장하기 전에 식별자(Id)를 지정한다는 것이다.
그리고 이 식별자는 CreateUser 명령을 통해 전달된다.
즉 CreateUserAsync() 메서드가 호출되기 전에 식별자가 정해진다.
이 조건을 만족하기 위해 엔터티 식별자 형식을 Guid로 변경한다.
Guid를 식별자 형식으로 사용하면 엔터티가 데이터 저장소에 영속되기 전에 식별자를 결정하면서도 기존 엔터티의 식별자와 중복되지 않음을 보장할 수 있다.
CQRS에서는 일반적으로 Guid를 엔터티 식별자로 사용한다.

CQRS의 장점

  • 각각 도메인 목적에 맞게 개발 할 수 있다.
  • 명령과 쿼리 파이프라인을 원하는대로 최적화 하며 다른 요소가 깨질 위험이 거의 없다.

CQRS의 단점

  • 구현해야 할 코드가 많아진다.
  • 더 많은 구현 기술이 필요해진다.
  • 유지 비용이 증가한다.

디미터 법칙

Object-Oriented Programming:An Objective Sense of Style 에서 처음 소개된 개념

객체 간 관계를 설정할 때 객체 간의 결합도를 효과적으로 낮출 수 있다.

객체 구조의 경로를 따라 멀리 떨어져 있는 낯선 객체에 메시지를 보내는 설계를 피하라는 것이다.

Don't Talk to Strangers

public class Post {
    private final List<Comment> comments;

    public Post(List<Comment> comments) {
        this.comments = comments;
    }

    public List<Comment> getComments() {
        return comments;
    }
}
public class Board {
    private final List<Post> posts;

    public Board(List<Post> posts) {
        this.posts = posts;
    }

    public void addComment(int postId, String content) {
        posts.get(postId).getComments().add(new Comment(content));
    }
    ...
}

addComment 메소드를 보면 Board 객체의 인스턴스 변수 posts 에서 getter를 거듭하여 객체 Comment를 추가하는 코드이다.
이렇게 getter가 계속 이어지는 코드는 디미터 법칙을 위반하는 코드이다.

만약 Post객체에서 인스턴스 변수가 List comments에서 Comments라는 일급컬렉션 객체로 수정된다면 getter을 통해 List comments를 사용하던 Board 객체의 addComment 메서드가 깨지게 된다.

디미터 법칙에서는 노출 범위를 제한하기 위해

  • 객체 자신의 메소드를
  • 메소드의 파라미터로 넘어온 객체들의 메소드들
  • 메소드 내부에서 생성, 초기화 된 객체의 메소드들
  • 인스턴스 변수로 가지고 있는 객체가 소유한 메소드들

에 해당하는 메소드를 호출해야 한다고 한다.

class Demeter {
    private Member member;

    public myMethod(OtherObject other) {
        // ...
    }

    public okLawOfDemeter(Paramemter param) {
        myMethod();     // 1. 객체 자신의 메서드
        param.paramMethod();    // 2. 메서드의 파라미터로 넘어온 객체들의 메서드
        Local local = new Local();
        local.localMethod();    // 3. 메서드 내부에서 생성, 초기화된 객체의 메서드
        member.memberMethod();   // 4. 인스턴스 변수로 가지고 있는 객체가 소유한 메서드
    }
}

일급 컬렉션

콜렉션을 Wrapping 하면서, 그 외 다른 멤버 변수가 없는 상태

이 코드를

Map<String, String> map = new HashMap<>();
map.put("1", "A");
map.put("2", "B");
map.put("3", "C");

아래와 같이 wrapping 하는 것

public class GameRanking {

    private Map<String, String> ranks;

    public GameRanking(Map<String, String> ranks) {
        this.ranks = ranks;
    }
}

다른 예시

public class Car {
    private int position;
    
    public void move(MovingStrategy movingStrategy){
        if(movingStrategy.isMove()){
        position++;
        }
    }
    
    public int getPosition() {
    return position;
    }
}
Car a = new Car();
Car b = new Car();
Car c = new Car();

일급컬렉션인 Cars 클래스로 함께 관리

public class Cars{

    private List<Car> cars;
    
    public Cars(List<Car> cars){
    this.cars=this.cars;
    }
    
    public List<Car> getCars(){
    return cars;
    }

}

일급 컬렉션의 이점

  1. 비지니스에 종속적인 자료구조
  2. Collection의 불변성을 보장
  3. 상태와 행위를 한 곳에서 관리
  4. 이름이 있는 컬렉션

0개의 댓글