-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.json
More file actions
1 lines (1 loc) · 214 KB
/
index.json
File metadata and controls
1 lines (1 loc) · 214 KB
1
[{"content":"DDD START SERIES는 도메인 주도 개발 시작하기 책을 참고 하여 작성된 요약(?) 글 입니다.\n8.1 애그리거트와 트랜잭션. 애그리거트의 일관성이 깨지면 안된다.\n깨지는 문제가 발생하지 않으려면 2가지 방법이 있다.\n A 수정하고 있는 동안 B에서 수정할 수 없게 막는다. A가 조회를 하고 B가 변경을 하면 A는 B의 수정이 끝나면 재조회를 해서 수정한다. 이 방법들은 애그리커트의 자체의 트랜잭션과 관련이 있다.\nDBMS가 지원하는 트랜잭션과 함께 애그리거트를 위한 추가적인 트랜잭션 처리 기법이 필요하다.\n대표적인 트랜잭션 처리 방법은 선점 잠금과 비선점 잠금의 두가지 방식이 있다.\n8.2 선점 잠금 선점 잠금은 먼저 애그리커트를 구한 스레드가 애그리거트 사용이 끝날때까지 다른 스레드가 해당 애그리거트를 수정하지 못하게 막는 방식이다.\n8.2.1 선점 잠금과 교착 상태 선점 잠금을 사용할땐 잠금 순서에 따른 교착 상태가 발생하지 않도록 주의해야한다.\n선점 잠금에 따른 교착 상태는 사용자 수가 많을때 발생할 가능성이 높다.\n또한 교착 상태에 빠지는 스레드도 빠르게 증가한다.\n이런 문제를 방지하기 위해 최대 대기 시간을 지정해야 한다.\nSpring Data Jpa는 @QueryHints 애노테이션을 사용한다.\nDBMS에 따라 교착 상태에 빠진 커넥션을 처리하는 방식이 다르기 때문에 사용하는 DBMS에 대해 JPA가 어떤 식으로 대기 시간을 처리하는지 반드시 확인해야 한다.\n8.3 비선점 잠금 선전 잠금이 강력해 보이긴 하지만 선점잠금으로 모든 트랜잭션 충돌 문제가 해결되는 것은 아니다.\n위의 그림과 같은 상황에서는 선점 잠금으로 해결할 수 없다.\n이런 문제는 DBMS 반영 시점에 가능 여부를 확인하여 변경한다.\n다음과 애그리거트에 버전을 넣어 수정할때 마다 버전으로 사용할 프로퍼티 값이 1씩 증가하게 구현하면 된다.\n이렇게 되면 다른 두 스레드간에 잘못된 데이터 수정을 방지 할 수 있다.\n8.4 오프라인 선점 잠금 선점 잠금이나 비선전 잠중으로 해결할 수 없을때 필요한 것이 오프라인 선점 잠금이다.\n단일 트랜잭션에서 막는 선점 잠금과는 달리 여러 트랜잭션에서 동시 변경을 막는다.\n첫 번째 트랜잭션은 시작하면 오프라인 잠금을 선점하고 마지막 트랜잭션에서 잠금을 해제한다.\n잠금 해제 전까지 다른 사용자는 잠금을 해제 할 수 없다.\n그림을 보면 A가 과정 3의 수정 요청을 수행하지 않고 프로그램을 종료하게 되면 잠금을 해제하지 않으므로 다른 사용자는 영원히 잠금을 구할 수 없다.\n그렇기 때문에 오프라인 선점 방식에는 잠금 유효 시간이 필요하다.\n유효시간이 지나면 자동으로 잠금을 해제해 다른 사용자가 잠금을 구할 수 있도록 해야 한다.\n오프라인 선점 잠금을 위한 방법으로는\nLockManager 인터페이스와 관련 클래스, DB를 이용한 LockManager 구현 방법이 있다.\nREFERENCE 도메인 주도 개발 시작하기\n ","permalink":"https://www.springboot.kr/posts/java/start-ddd/ep8-start-ddd/","summary":"DDD START SERIES는 도메인 주도 개발 시작하기 책을 참고 하여 작성된 요약(?) 글 입니다.\n8.1 애그리거트와 트랜잭션. 애그리거트의 일관성이 깨지면 안된다.\n깨지는 문제가 발생하지 않으려면 2가지 방법이 있다.\n A 수정하고 있는 동안 B에서 수정할 수 없게 막는다. A가 조회를 하고 B가 변경을 하면 A는 B의 수정이 끝나면 재조회를 해서 수정한다. 이 방법들은 애그리커트의 자체의 트랜잭션과 관련이 있다.\nDBMS가 지원하는 트랜잭션과 함께 애그리거트를 위한 추가적인 트랜잭션 처리 기법이 필요하다.","title":"[DDD START SERIES] EP.8 애그리거트 트랜잭션 관리"},{"content":"DDD START SERIES는 도메인 주도 개발 시작하기 책을 참고 하여 작성된 요약(?) 글 입니다.\n7.1 여러 애그리거트가 필요한 기능 한 애그리거트에 넣기 애매한 도메인 기능을 억지로 특정 애그리거트에 구현하면 안 된다.\n억지로 구현할 경우 애그리거트 자신의 책임 범위를 넘어서는 기능을 구현한다.\n때문에 코드가 길어지고 외부에 대한 의존이 높아지게 되며 코드를 복잡하게 만들어 수정을 어렵게 만든다.\n게다가 애그리거트의 범위를 넘어서는 도메인 개념이 애그리거트에 숨어들어 명시적으로 드러나지 않게 된다.\n이러한 문제를 해결하기 쉬운 해결 방법은 도메인 기능을 별도의 서비스로 만드는 것이다.\n7.2 도메인 서비스 도메인 서비스는 도메인 영역에 위치한 도메인 로직을 표현할 때 사용한다.\n주로 아래와 같은 경우에 도메인 서비스를 생성한다.\n 계산 로직 - 여러 애그리거트가 필요한 계산 로직. 한 애그리거트에 넣기에 다소 복잡한 로직. 외부 시스템 연동이 필요한 도메인 로직 - 구현하기 위해 타 시스템을 사용해야 하는 도메인 로직. 7.2.1 계산 로직과 도메인 서비스 한 애그리거트에 넣기 애매한 도메인 개념을 구현하려면 애그리거트에 억지로 넣기보다는 도메인 서 버스를 이용해 드메인 개념을 명시적으로 들어낸다.\n응용 영역의 서비스가 응용 로직을 다룬다면 도메인 서비스는 도메인 로직을 다룬다.\n도메인 영역의 애그리거트나 벨류와 같은 구성요소와 도메인 서비스를 비교할 때 다른 점은 도메인 서비스는 상태 없이 로직만 구현한다는 점이다.\n그렇기 때문에 서비스 구현에 필요한 상태는 다른 방법으로 전달받는다.\n도메인 서비스는 도메인의 의미가 드러나는 용어를 타입과 메서드 이름으로 갖는다.\n애그리거트 객체에 도메인 서비스를 전달하는 것은 응용 서버스 책임이다.\n주의할 점은 도메인 서비스를 에그리거트에 주입해서는 안 된다.\n주입한다는 건 곳 의존한다는 것을 의미하기 때문이다.\n이는 프레임워크의 기능을 사용하고 싶은 개발자의 욕심을 채우는 것에 불과하다.\n도메인 서비스는 도메인 로직을 수행하는 것이지 응용 로직을 수행하는 것이 아니다.\n트랜잭션 처리 같은 로직은 응용 로직이므로 도메인 서비스가 아닌 응용 서비스에서 처리해야 한다.\n7.2.2 외부 시스템 연동과 도메인 서비스 7.2.3 도메인 서비스의 패키지 위치 도메인 서비스의 개수가 많거나 엔티티나 밸류와 같은 다른 구성요소와 명시적으로 구분하고 싶다면 domain 패키지 밑에 model, service, repository 와 같은 하위 패키지를 구분하여 위치 시켜도 된다.\n7.2.4 도메인 서비스의 인터페이스와 클래스 도메인 서비스의 로직이 고정되어 있지 않은 경우 도메인 서비스 자체를 인터페이스로 구현하고 이를 구현한 클래스를 둘 수도 있다.\n특히 도메인 로직을 외부 시스템이나 별도 엔진을 이용해서 구현할 때 인터페이스와 클래스를 분리하게 된다.\nREFERENCE 도메인 주도 개발 시작하기\n ","permalink":"https://www.springboot.kr/posts/java/start-ddd/ep7-start-ddd/","summary":"DDD START SERIES는 도메인 주도 개발 시작하기 책을 참고 하여 작성된 요약(?) 글 입니다.\n7.1 여러 애그리거트가 필요한 기능 한 애그리거트에 넣기 애매한 도메인 기능을 억지로 특정 애그리거트에 구현하면 안 된다.\n억지로 구현할 경우 애그리거트 자신의 책임 범위를 넘어서는 기능을 구현한다.\n때문에 코드가 길어지고 외부에 대한 의존이 높아지게 되며 코드를 복잡하게 만들어 수정을 어렵게 만든다.\n게다가 애그리거트의 범위를 넘어서는 도메인 개념이 애그리거트에 숨어들어 명시적으로 드러나지 않게 된다.\n이러한 문제를 해결하기 쉬운 해결 방법은 도메인 기능을 별도의 서비스로 만드는 것이다.","title":"[DDD START SERIES] EP.7 도메인 서비스"},{"content":"OCP란? OCP (Open-Closed Principle)는 소프트웨어 디자인 원칙 중 하나로, 소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다는 원칙을 나타냅니다. 이 원칙은 로버트 C. 마틴(Robert C. Martin)이 개발한 SOLID 원칙 중 하나로, 소프트웨어 시스템을 유지보수하고 확장하기 쉽게 만드는 데 기여합니다.\nOCP의 주요 개념은 다음과 같습니다.\n확장에는 열려 있어야 함(Open for Extension): 소프트웨어 엔티티(클래스 또는 모듈)는 새로운 기능을 추가하거나 변경할 수 있어야 합니다. 이것은 새로운 요구 사항이나 기능 추가에 대응하기 위한 확장 가능성을 의미합니다.\n변경에는 닫혀 있어야 함(Closed for Modification): 소프트웨어 엔티티는 이미 동작 중인 코드를 변경하지 않아야 합니다. 새로운 기능을 추가하거나 수정하기 위해서는 이미 작성된 코드를 변경하지 않고도 가능해야 합니다.\n스프링 프레임워크는 OCP 원칙을 따르도록 설계되었습니다. 스프링은 인터페이스, 추상 클래스, 의존성 주입(Dependency Injection), AOP(Aspect-Oriented Programming) 등을 통해 시스템을 확장 가능하고 변경에 닫힌 형태로 구성합니다. 이러한 디자인은 스프링 애플리케이션을 유지보수하고 확장하기 쉽게 만들어주며, 새로운 기능을 추가하거나 기존 기능을 수정할 때 코드의 안정성과 일관성을 유지하는 데 도움이 됩니다.\n그럼 어떻게 하면 좋을 수 있을까?\n예제로 은행에 조회를 요청하는 메서드를 작성 중 각 은행의 요구사항에 맞는 컷오프를 설정할때를 얘로 들어 볼 예정이다.\nOCP 체험 해보기 추상화 우선 다음과 같이 BankCutOff라는 interface로 추상화를 구현한다.\npublic interface BankCutOff { boolean isSameBank(Bank bank); boolean isCutOff(); } 그다음으로 은행을 판별할 enum을 생성해 주고\nimport lombok.AllArgsConstructor; import lombok.Getter; @AllArgsConstructor @Getter public enum Bank { HANA, SC, WOORI, ; } 추상화 구현 그다음으로 BankCutOff를 구현하는 구현체 3가지를 구현해 보자.\n@Service @Slf4j public class HanaBankCutOff implements BankCutOff { private static final String BANK_NAME = \u0026#34;HANA_BANK\u0026#34;; @Override public boolean isSameBank(Bank bank) { return bank == Bank.HANA; } @Override public boolean isCutOff() { log.info(\u0026#34;하나 은행 컷오프 실행\u0026#34;); return true; } } @Service @Slf4j public class ScBankCutOff implements BankCutOff { @Override public boolean isSameBank(Bank bank) { return bank == Bank.SC; } @Override public boolean isCutOff() { log.info(\u0026#34;제일 은행 컷오프 실행\u0026#34;); return false; } } @Service @Slf4j public class WooriBankCutOff implements BankCutOff { @Override public boolean isSameBank(Bank bank) { return bank == Bank.WOORI; } @Override public boolean isCutOff() { log.info(\u0026#34;우리 은행 컷오프 실행\u0026#34;); return true; } } 다음과 같이 구현한 후\n핸들러 구현 마지막으로 아래와 같은 핸들러를 하나 구현해 보자\n@RequiredArgsConstructor @Component public class BankCutOffHandler { private final Set\u0026lt;BankCutOff\u0026gt; bankCutOffs; public boolean getBankCutOff(Bank bank) { BankCutOff cutOff = bankCutOffs.stream() .filter(bankCutOff -\u0026gt; bankCutOff.isSameBank(bank)) .findFirst() .orElseThrow(() -\u0026gt; new IllegalArgumentException(\u0026#34;BAD REQUEST\u0026#34;)); return cutOff.isCutOff(); } } 테스트 코드 작성 테스트 코드를 다음과 같이 작성 후 결과를 확인해보면~~~~~~\n결과 잘 실행되는 걸 알 수 있다.\n","permalink":"https://www.springboot.kr/posts/spring/ocp-pracitce/","summary":"OCP란? OCP (Open-Closed Principle)는 소프트웨어 디자인 원칙 중 하나로, 소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다는 원칙을 나타냅니다. 이 원칙은 로버트 C. 마틴(Robert C. Martin)이 개발한 SOLID 원칙 중 하나로, 소프트웨어 시스템을 유지보수하고 확장하기 쉽게 만드는 데 기여합니다.\nOCP의 주요 개념은 다음과 같습니다.\n확장에는 열려 있어야 함(Open for Extension): 소프트웨어 엔티티(클래스 또는 모듈)는 새로운 기능을 추가하거나 변경할 수 있어야 합니다. 이것은 새로운 요구 사항이나 기능 추가에 대응하기 위한 확장 가능성을 의미합니다.","title":"스프링으로 OCP 야무지게 체험해보기"},{"content":"DDD START SERIES는 도메인 주도 개발 시작하기 책을 참고 하여 작성된 요약 글 입니다.\n6.1 표현 영역과 응용 영역 도메인이 제 기능을 하려면 사용자와 도메인을 연결해 주는 매개체가 필요하다.\n응용 영역과 표현 영역이 사용자와 도메인을 연결해 주는 매개체 역할을 한다.\n사용자 → 표현 영역 → 응용 영역 → 도메인 영역\n표현 영역은 사용자의 요청을 해석한다.\n해석 후 원하는 기능을 판별하여 기능을 제공하는 응용 서비스를 실행한다.\n실제 사용자가 원하는 기능을 제공하는 것은 응용 영역에 위치한 서비스다.\nex) 사용자가 회원 가입을 요청했다면 실제 요청을 위한 기능을 제공하는 주체는 응용 서비스에 위치한다.\n응용 서비스는 기능을 실행하는데 필요한 입력값은 메서드 인자로 받고 실행 결과를 리턴한다.\n표현 영역은 응용 서비스가 요구하는 형식으로 사용자 요청을 변환한다.\n6.2 응용 서비스의 역할 공용 서비스는 사용자가 요청한 기능을 실행한 그러기 위해 리포지터리에서 도메인 객체를 가져와 사용한다.\n표현 영역 입장에선 도메인 영역과 연결해 주는 브릿지(응용 영역) 역할이다.\n응용 서비스는 주로 도메인 객체 간의 흐름을 제어하기에 단순한 형태를 가진다.\nex) 1. 애그리거트를 구한다. 2. 애그리거트의 도메인 기능을 실행한다. 3. 결과를 리턴한다. 만약 응용 서비스가 복잡하다면 응용 서비스에서 도메인 로직의 일부를 구현하고 있을 가능성이 높다.\n응용 서비스가 도메인 로직을 구현하면 코드 중복, 로직 분산 등 코도 품질에 안 좋은 영향을 줄 수 있다.\n또한 응용 서비스는 트랜잭션 처리도 담당하며 응용 서비스는 도메인의 상태 변경을 트랜잭션으로 처리해야 한다.\n트랜잭션 외 주요 역할로 접근 제어와 이벤트 처리가 있다.\n6.2.1 도메인 로직 넣지 않기 응용 서비스는 도메인 로직을 구현하면 안 된다.\n도메인 로직은 도메인 영역과 응용 영역에 분산 구현하면 코드 품질에 다음과 같은 문제가 생긴다.\n 응집도가 떨어진다.\n 여러 응용 서비스에서 동일한 로직을 구현할 가능성이 높다.\n 이러한 문제점들은 응집도가 떨어지고 코드 중복이 발생하는 코드를 만들게 된다.\n그럼 코드 변경이 어렵고 소프트웨어의 가치가 떨어지게 되는데 떨어지지 않고 가치를 높이기 위해선 위의 문제점들을 제외하고 구현하면 응집도를 높이고 코드의 중복을 막을 수 있다.\n6.3 응용 서비스의 구현 응용 서비스는 표현 영역과 도메인 영역을 연결한 매개체 역할을 한다.(이는 디자인 패턴의 퍼사드와 같은 역할을 한다.)\n응용 서비스는 복잡한 로직은 하지 수행하지 않기 때문에 구현은 어렵지 않다.\n6.3.1 응용 서비스의 크기 응용 서비스는 구현이 어렵지 않지만 생각할게 몇 개 있는데 그중 하나가 응용 서비스의 크기다.\n 응용 서비스 클래스에 도메인의 모든 기능 구현하기\n 구분되는 기능별로 응용 서비스 클래스를 따로 구현하기\n 한 도메인 관련 기능을 하나의 클래스에 구현하면 동일한 로직에 대한 중복을 없앨 수 있다.\n이게 장점이지만 단점은 클래스에 기능이 몰려 있어 클래스가 커질 수가 있다.\n그럼 결국엔 연관성이 없는 코드끼리 뭉쳐서 코드가 보기 힘들 수 있다.\n또한 한 클래스에 모든 기능을 구현하면 엄연히 분리하는 게 좋아 보임에도 습관적으로 기존 코드에 억지로 끼워 넣게 된다.\n이것은 코드를 점점 얽히게 만들어 코드 품질을 낮춘다.\n그렇기 때문에 구분되는 기능별로 서비스 클래스를 구현하는 방식은 한 응용 서비스 클래스에 한 개 내지 2~3개의 기능을 구현한다.\n이 방식을 사용하면 클래스는 많아지지만 코드의 품질은 일정 수준 유지된다.\n또한 각 클래스 별로 필요한 의존 객체만 포함하므로 다른 기능을 구현한 코도에 영향을 받지 않는다.\n각 기능마다 동일한 로직을 구현할 경우 별도의 클래스를 구현해 사용할 수 있다.\nex)\npublic final userServiceHelper { public static User findBy(String name) { // ~~~~ } } 6.3.2 응용 서비스의 인터페이스와 클래스 인터페이스가 필요한 경우는 구현체가 여러 개인 경우인데 하지만 응용 서비스는 구현체가 두 개인 경우도 드물다.\n이러한 이유로 인해 인터페이스와 구현체를 타고 구현하면 소스 파일만 많아지고 구현 클래스에 대한 간전 참조가 증가해 전체 구조가 복잡해지게 된다.\n따라서 인터페이스가 명확하게 필요하기 전까지는 응용 서비스에 대한 인터페이스를 작성하는 것이 좋은 선택이라고 볼 수 없다.\n6.3.3. 메서드 파라미터와 값 리턴 응용 서비스로의 값 전달에서 파라미터가 2개 이상인 경우 클래스를 만들어 전달하는 것이 편리하다.\n또한 표현 영역에서 응용 서비스의 결과로 사용해야 하는 경우 응용 서비스에 에서의 결과로 필요한 데이터를 리턴한다.\nex) 상품 주문 후 주문상 세 내역을 조회하기 위해 주문번호를 리턴한다.\n거기에 에그리거트를 리턴할 수도 있지만 필요한 값만 재정의 후 리턴하는 것이 좋으며 응집도를 높일수있다.\n6.3.4 표현 영역에 의존하지 않기 응용 서비스의 파라미터 타입을 결정할 때는 표현 영역과 관련된 타입을 사용하면 안 된다.\n그 예로 HttpServletRequest 나 HttpSession을 응용 서비스로 전달하면 안 된다.\n이렇게 되면 단독으로 테스트하기가 어려워지며 표현 영역이 변경되면 응용 서비스 영역도 변경해야 할 수 있다.\n더 큰 문제는 응용 서비스가 표현 영역을 대신할 수 있기 때문에 표현 영역의 응집도가 깨지게 돼 결과적으로 유지 보수 비용이 발생한다.\n이러한 문제가 발생하지 않으려면 응용 서비스가 표현 영역의 기술은 사용하지 않도록 해야 한다.\n6.3.5 트랜잭션 처리 트랜잭션은 프레임워크가 제공하는 트랜잭션 기능을 적극 사용하는 것이 좋다.\n스프링에서 제공하는 @Transaction을 사용해 코드를 작성하면 트랜잭션 처리 코드를 간결하게 유지할 수 있다.\n6.4 표현 영역 표현 영역의 역할 3가지\n 사용자에게 화면 제공 및 제어.\n 사용자의 요청에 맞는 응용 서비스 전달 및 결과 제공.\n 사용자의 세션 관리.\n 6.5 값 검증 값 검증 시 단일로 검증하지 않고 여러 개를 한 번에 검사하고 에러 목록에 추가하여 사용자에게 번거로움을 제공하지 않을 수 있다.\n스프링에서는 Validator 인터페이스를 제공하기에 인터페이스를 사용한 검증기를 만들 수 있다.\nLayer 별 검증의 범위\n 표현 영역 - 필수 값, 값의 형식, 변위 검증.\n 응용서비스 - 데이터의 유무 존재, 논리적 오류 검증.\n 하지만 이 책의 글쓴이는 요즘 응용 서비스에서 모든 걸 처리한다고 하며 이는 코드가 늘어가는 불편함이 있지만 응용 서비스의 완성도 높아지기 때문이라고 말한다.\n6.6. 권한 검사 권한 검사는 보통 세 곳에서 이루어진다.\n 표현 영역\n 응용 서비스\n 도메인\n 표현 영역은 기본적인 인증된 사용자를 검사한다.\n예로 회원정보를 변경할 때 이와 관련된 URL로 접근한 사용자가 인증된 사용자인지 확인한다.\n만약 URL로 정자는 접근을 제어할 수 없는 경우 응용 서비스에서 메서드 단위로 권한 검사를 수행한다.\n꼭 코드상 검사가 아니더라도 스프링 시큐리티 AOP 활용한 검중도 사용할 수 있다.\n개별 도메인 객체 단위로의 검사의 경우 구현이 복잡해진다.\n스프링 시큐리티 같은 보안 프레임워크를 사용해 프레임워크에 통합할 수도 있다.\n하지만 보안 프레임워크를 확장하려면 프레임워크에 대한 높은 이해도가 필요하다.\n그렇지 않다면 직접 구현하는 것이 코드 유지 보수에 유리하다.\n6.7 조회 전용 기능과 응용 서비스 조회의 기능만 있는 서비스는 차라리 구현하지 않는 게 좋을 수도 있다.\n바로 표현 영역에 구현해도 문제가 없다.\nREFERENCE 도메인 주도 개발 시작하기\n ","permalink":"https://www.springboot.kr/posts/java/start-ddd/ep6-start-ddd/","summary":"DDD START SERIES는 도메인 주도 개발 시작하기 책을 참고 하여 작성된 요약 글 입니다.\n6.1 표현 영역과 응용 영역 도메인이 제 기능을 하려면 사용자와 도메인을 연결해 주는 매개체가 필요하다.\n응용 영역과 표현 영역이 사용자와 도메인을 연결해 주는 매개체 역할을 한다.\n사용자 → 표현 영역 → 응용 영역 → 도메인 영역\n표현 영역은 사용자의 요청을 해석한다.\n해석 후 원하는 기능을 판별하여 기능을 제공하는 응용 서비스를 실행한다.\n실제 사용자가 원하는 기능을 제공하는 것은 응용 영역에 위치한 서비스다.","title":"[DDD START SERIES] EP.6 응용 서비스의 구현"},{"content":"DDD START SERIES는 도메인 주도 개발 시작하기 책을 참고 하여 작성된 요약 글 입니다.\nEP 3.1 애그리거트 공통된 모델을 애그리커트 단위로 묶어서 표현한다. 보기도 쉬워지고 좋다.\n상위 수준에서 모델화 모델을 정리하면 도메인의 복잡한 관계가 더 이해가 잘 된다.\n애그리거트를 단위로 묶으면 이해하는 것뿐아니라 일관성 관리에 기준이 된다. 복잡도를 줄이고 변경 시간을 줄어들게 만든다.\n애그리거트로 묶인 도메인들은 함께 생성하고 함께 제거한다.\n주문으로 묶인 집합은 애그리거트는 회원이란 애그리거트의 영역을 침범하지 않는다.\n경계를 설정할때 기본은 도메인의 규칙과 요구사항이다.\n값이 같이 변경되는 경우는 같은 애그리거에도에 속할 가능성이 높다\n처음 도메인을 만들면 큰애그리거트로 보이는 것이 많지만 어느정도 도메인에 대한 이해가 쌓이면 애그리지트의 실제 크기는 줄어든다.\n애그리거모를 잘 쪼개 묶어야 유지회에 용이하다. 애그리워트는 같은 역할 군집에 넣을 수 있다.\nEP 3.2 애그리커트 루트 애그리거트는 여러 객체로 구성된다.\n그렇기에 한 객체만 상태가 정상이면 안 되고 모두가 정상이여야 한다.\n3.2.1 도메인 규칙과 일관성 애그리거트 루트는 애그리거트의 일관성이 깨지지 않도록하는것이다.\n이를 위해 애그리거트 루트는 애그리거트가 해야할 도메인 기능을 구현한다.\n애그리거트 루트가 제공하는 메서드는 도메인규칙에 따라 애그리거트에 속한 객체의 일관성이 깨지지 않도록 구현한다.\n애그리거트 외부에서 애그리거트에 속한 객체를 직접 변경하면 안된다.\n이것은 애그리거트 루트가 강제하는 규칙을 적용할 수 없어 모델의 일관성을 깬다.\n불필요한 중복을 피하고. 애그리거트 루트를 통해서만 도메인 로직을 구현하게 만들기 위해 두가지 습관이 필요하다.\n set 메서드는 public으로 만들지 마라. 벨류 타입은 불변으로 만든다. (불변타입은 외부에서 수정이 불가하다.) 그렇기에 변경할수 있는 방법은 새로운 벨류 객체를 생성하는 것 뿐이다.\n그럼 결국 애그리거트 루트를 써야 하기에 객체의 일관성이 유지된다.\n3.2.2 애그리거트 루트의 기능 구현 애그리거트 루트는 애그리거트를 조합해서 기능을 완성하게 된다.\n애그리거트 루트에서 조합을해 기능을 만들고 외부에서 가져다 쓰는 방식으로 사용하도록 하자.\n만약 외부에서도 사용하고 싶지 않다면 접근제어자를 protected로 선언하여 막아보자.\n3.2.3 트랜잭션의 범위 트랜잭션의 범위는 작을수록 좋다. 트랙잭션당 1개의 테이블로 잡고가는것이 성능에 좋다.\n하나의 트랜잭션에는 하나의 애그리거트의 그룹만 있는 있는것이 좋다.\n거기다 다른 애그리거트의 기능에 의존하게 되면 코드의 결합도가 증가하고 수정 비용도 증가한다.\n부득이하게 한 트랜잭션으로 두개이상의 애그리거트를 써야 하는경우 직접 수정하지 말고 응용 서버스에서 수정하도록 구현한다.\n물론 예외는 있다.\nex) 팀 표준, 기술 제약, UI 구현의 편리.\n3.3 리포지터리와 애그리거트 애그리거트는 레포지토리와 쌍이다. / 개념적으로 다가.\n애그리거트는 개념적으로 하나이므로 리포지터리는 애그리거트 전체를 저장소에 영속화해야한다.\n애그리거트 루트 말고 이 전체 그룹의 모든 값을 저장 변경해야한 리포지터리가\n만약 완전한 애를 제공하지 않으면 필드나 값이 올바르지 않아 애그리거트의 기능을 실행하는 도중 NPE와 같은 문제가 발생할 수 있다.\n•애그리거트는 리모리터리와 쌍을 맺어요킨다. 애려구트의 그룹 전체를 영속화하자.\n3.4 3.4 ID를 이용한 애그리커트 다른 애그리커트를 참조한다는 것은 다른 애그리커트 루트를 참조 한다는 것이다.\n예로 주문에서 회원을 참조하기 위해 애그리커트 루트 멤버를 필드로 참고 할수있다.\n하지만 필드를 이용한 애그리거트 참조는 다음과 같은 3가지의 문제를 야기할 수 있다.\n첫번째로 애그리커트를 참조할때 가장 큰 문제는 편리함 때문에 오용할 수 있다.\n애그리커트 내부에서 다른 애그리커트 객체에 접근 할 수 있으면 상태를 쉽게 변경할 수 있다.\n트랜적션 범위로 애그리커트가 관리하는 범위는 자기 자신으로 해야 한다.\n근데 접근 할 수 있으면 다른 애를 수정하고자 하는 유혹에 빠질 수 있고 이러한 결과를 결합도를 높인다.\n두번째로 JPA를 사용할때 즉시 로딩의 경우에는 조회 성능에는 유리하지만 상태를 변경하는 기능을 실행하는 경우 에는 불필요한 객체를 함께 로딩하기에 지연 로딩이 유리할 수 있다.\n세번째로 확장으로 단일 기술의 문제도 발생할 수 있다. 마지아를 쓰다 몽고를 써서 다 다른 기술을 쓰는 경우가 있을수 있다.\n이런 문제들을 완화할때 쓰는 것이 ID값을 이용해 다른 애를 참조하는 것이다.\n이는 DB의 외래키와 비슷하다.\n객체의 참조만 이루어지면 되기에 이는 경계를 명확하게 하고 결합도를 낮춘다.\n애그리커트간의 의존을 제거하므로 응집도를 높여주는 효과가 있으며 구현 복잡도도 또한 낮아진다.\n참조가 필요할 경우 응용 서비스 단계에서 ID를 참조하여 로딩하면 애그리거트 수준에서는 지연 로딩과 비슷한 동일한 결과를 얻는다.\nID 참조 방식은 복잡도를 낮추고 다른 애그리거트를 수정하는 문제를 근원적으로 방지할 수 있다.\n또한 애그리거트 마다 다른 구현 기술을 사용할 수도 있어 확장이 용이하다.\n3.4.1 ID를 이용한 참조와 조회 성능 다른 애그리거트의 ID를 참조하게 되면 참조하는 여러개의 애그리거트를 읽을때 속도에 문제가 생길 수 있다.\nN+1의 조회 문제가 발생할 수 있고 이는 후후 많은 쿼리를 실행하기에 전체 조회 속도를 느리게 만든다.\n이런 문제를 위해 조인을 사용한다.\n하지만 조인은 ID 참조 방식을 객체 참조 방식으로 바꾸기에 이를 해결하기 위해 전용 쿼리를 사용하면 된다.\n혹여나 서로 다른 저장소를 사용한다면 조회 성능을 높이기 위해 캐시를 적용하거나 조회 전용 저장소를 따로 구성하도록 한다.\n3.5 애그리거트 간 집합 연관 애그리거트의 대표적인 연관 관계로는 1-N, M-N 가 있다.\n대표적인 예로 카테고리와 상품으로 예시를 들 수 있는다.\n1-N 관계는 Set으로 표현할 수 있다.\n하지만 1-N을 set으로 설계해놓으면 상품의 계수가 수만 건일 경우 성능 저하가 발생할 수 있다.\n그렇기에 실제 구현에는 반영하지 않고 거꾸로 상품에 카테고리를 구현한다.\nM-N은 개념적으로 양쪽 애그리거트에 컬렉션으로 연관을 만든다.\n개념에는 양방향으로 존재하지만 실제 구현에는 상품에서 카테고리로의 단방향 M-N 연관만 적용하면 된다.\n3.6 에그리거트를 팩토리로 사용하기 상품을 등록할 수 있는 기능을 구현할 때 애그리 거스를 분리하여 Store와 produce를 분리해 Store에서 product를 생성하는 팩토리를 구현할 수 있다.\n이렇게 하면 Store의 코드만 변경하면, 응용 서비스는 영향을 받지 않는다.\n그로 인해 도메인의 응집 도도 높아진다. 이것이 애그리거트를 팩토리로 사용할 때의 장점이다.\n애그리거트 안에서 다른 애그리거트를 생성해야 할 때 팩토리 메서드를 활용해 보자.\n그러면 다른 애그리거트를 생성할 때 필요한 데이터를 직접 제공하면서 동시에 중요한 도메인 로직을 함께 구현할 수 있다.\nREFERENCE 도메인 주도 개발 시작하기\n ","permalink":"https://www.springboot.kr/posts/java/start-ddd/ep3-start-ddd/","summary":"DDD START SERIES는 도메인 주도 개발 시작하기 책을 참고 하여 작성된 요약 글 입니다.\nEP 3.1 애그리거트 공통된 모델을 애그리커트 단위로 묶어서 표현한다. 보기도 쉬워지고 좋다.\n상위 수준에서 모델화 모델을 정리하면 도메인의 복잡한 관계가 더 이해가 잘 된다.\n애그리거트를 단위로 묶으면 이해하는 것뿐아니라 일관성 관리에 기준이 된다. 복잡도를 줄이고 변경 시간을 줄어들게 만든다.\n애그리거트로 묶인 도메인들은 함께 생성하고 함께 제거한다.\n주문으로 묶인 집합은 애그리거트는 회원이란 애그리거트의 영역을 침범하지 않는다.\n경계를 설정할때 기본은 도메인의 규칙과 요구사항이다.","title":"[DDD START SERIES] EP.3 애그리거트"},{"content":"우선 생각이 나는 공부방법으로\n 책을 정리하면서 공부한다. 무작정 공부를하고 정리한다. 코넬노트 필기법 1번은 땡인것같음. 매번 시도해도 문서 정리하다 지쳐서 안하게됨.\n2번은 예전에 해봤는데 효과가 나름있고 집중이 잘됨.\n3번도 잠깐 하다 말았는데. 다시 시도해볼예정.\n1번은 땡이니 2,3번을 도전하고 다음 에 다시 포스팅해보도록하겠음.\n","permalink":"https://www.springboot.kr/posts/blahblah/2023-08-06-blah/","summary":"우선 생각이 나는 공부방법으로\n 책을 정리하면서 공부한다. 무작정 공부를하고 정리한다. 코넬노트 필기법 1번은 땡인것같음. 매번 시도해도 문서 정리하다 지쳐서 안하게됨.\n2번은 예전에 해봤는데 효과가 나름있고 집중이 잘됨.\n3번도 잠깐 하다 말았는데. 다시 시도해볼예정.\n1번은 땡이니 2,3번을 도전하고 다음 에 다시 포스팅해보도록하겠음.","title":"나한테 맞는 공부방법 찾아보기"},{"content":"근황 일을 하고 있다. GOOGLE 검색 보다 CHAT GPT가 편하다. TDD, 클린코드를 수강했다. ATDD, 클린코드를 수강하고 있다. 새로운 집에 왔다. 와이프도 한국에 왔다. 자세한 게시물은 곧 올릴예정이다.\n","permalink":"https://www.springboot.kr/posts/blahblah/2023-07-27-blah/","summary":"근황 일을 하고 있다. GOOGLE 검색 보다 CHAT GPT가 편하다. TDD, 클린코드를 수강했다. ATDD, 클린코드를 수강하고 있다. 새로운 집에 왔다. 와이프도 한국에 왔다. 자세한 게시물은 곧 올릴예정이다.","title":"오랜만에 글"},{"content":"이제 연도도 바뀌고 책을 보고 정리도 하는 것이 늘 거 같아서 책 요약하는 법을 찾아봤다.\n블로그에 책을 읽고 요약할 때 너무 많은 내용을 담으려 해서 시간이 너무 오래 걸리고 정리를 하고 나서도 금세 까먹어 다시 책을 보는 지경이니 책을 잘 요약할 수 있는 방법을 찾아봤다.\n\n 한 번만 읽어도 책 내용 다 기억하는 기적의 독서 메모 법 | 김익한 @ican 명지대 교수, 기록학자\n 내용을 정리하면 다음과 같다.\n 메모의 2가지 효능 외부로부터 들어오는 것을 효과적으로 흡수 내 안에 있는 것을 끄집어 내기 메모는 내 안에 있는 생각을 구체적으로 끄집어내는 최고의 수단이다.\n하지만 메모를 하는 것에도 종류가 있는데 내가 기존에 쓰던 방식은 속기사처럼 싹 다 적어버리는 방식이었다.\n이 속기사처럼 너무 많은 내용을 적게 되면 핵심을 놓칠 수 있다.\n그러기 위해서는 많이 쓰려는 욕심을 버리고 정말 핵심이 되는 액기스가 무엇인지를 생각해서 메모하려는 습관이 필요하다.\n다른 무언가를 메모하고 싶어도 참고 딱 키워드 2개만 메모를 하자 그렇게되면 정말 필요한 정보가 무엇인지 생각하게 되고 가치 있는 기록을 뽑아낼수있다.\n한 챕터를 요약할 때 노트 반쪽을 넘기지 않고 핵심 키워드만 메모하면서 읽으면 책의 큰 맥락이 명확히 잡히게 된다.\n그리고 이 메모를 바탕으로 자기화가 필요하다.\n즉 지식을 내 것으로 만드는 것이다.\n 지식을 내것으로 만드는 메모 법 많이 적지 마라 핵심 키워드를 내 방식으로 찾아내라 그러기 위해선 책에서 얻은 지식에 자신의 생각을 덧대어 정리하는 것이다.\n자신의 생각을 덧대 정리한 메모는 나의 기억에 남는 메모로 남을 수있다.\n 책의 맥락 읽기 메모 법 두 페이지를 읽은 후 책에서 눈을 떼라 핵심 키워드를 떠올려라 시선을 책에만 두게 되면 핵심을 찾지 않게 된다.\n그렇기에 책을 읽다 고개를 들고 핵심을 떠올린다. 그리고 다음 장 다음 장의 핵심 키워드를 연결해 맥락을 읽는다.\n책을 보면서 적지 않고 읽는 것이 끝나면 메모를 한다. 그 메모의 내용은 내가 혹한 내용을 메모하기 때문에 80% 이상 기억할 수 있다.\n책을 보다 \u0026lsquo;아!\u0026lsquo;하고 떠오른 것만이 자기화할 수 있는 지식이기 때문에 그 지식을 흡수할 수 있다.\n그리고 마지막으로 고민 해결 메모 법이라는 것이 있는데 한번 소개 해보려 하는데 이게 개발적으로 도움이 될 만한 메모 법이라 한번 보고 넘어가도 나쁘지 않을 것 같다.\n 고민 해결 메모 법 고민하게 된 배경 발생 원인 해결 방안 보통 개발 도중 막히는 부분이나 에러 처리 등 고민을 하게 될 경우\n고민하게 된 배경을 작성하고 발생 원인에 대해 적어 본다.\n그렇게 되면 내 현재 고민을 객관화해서 볼 수 있는데 이 고민을 객관화해보면 해결 방법도 떠올릴 수 있을 것 같다.\n물론 아직 경험하지 못하고 듣고 보기만 한 내용이라 한 번 실천해봐야 알수 있으니 이제 부터 해보려한다.\n\u0026mdash; 끗 \u0026mdash;\n","permalink":"https://www.springboot.kr/posts/blahblah/2023-01-08/","summary":"이제 연도도 바뀌고 책을 보고 정리도 하는 것이 늘 거 같아서 책 요약하는 법을 찾아봤다.\n블로그에 책을 읽고 요약할 때 너무 많은 내용을 담으려 해서 시간이 너무 오래 걸리고 정리를 하고 나서도 금세 까먹어 다시 책을 보는 지경이니 책을 잘 요약할 수 있는 방법을 찾아봤다.\n\n 한 번만 읽어도 책 내용 다 기억하는 기적의 독서 메모 법 | 김익한 @ican 명지대 교수, 기록학자\n 내용을 정리하면 다음과 같다.\n 메모의 2가지 효능 외부로부터 들어오는 것을 효과적으로 흡수 내 안에 있는 것을 끄집어 내기 메모는 내 안에 있는 생각을 구체적으로 끄집어내는 최고의 수단이다.","title":"기적의 독서 메모 법"},{"content":"새해 첫 주 핸드폰 보다가 알라딘에서 찜해둔 책이 들어왔길래 후다닥 가서 사 왔음.\n자바 퍼즐러\n자바 네트워크 프로그래밍\n이렇게 2개 샀는데 둘 다 절판이라 못 구하는 건데 알라딘에 들어왔길래 후다닥 가서 사 왔다.\n네트워크 프로그래밍은 수유에 가서 퍼즐로는 노원에 가서 사고 집에 왔다.\n집에 돌아오는 길에 크로켓을 팔길래 너무 먹고 싶었지만 참고 집에 와서 맛있는 걸 먹었다.\n이제 이거 쓰고 밖에 나가서 강아지 산책해야 하는데 지금 미세먼지 미쳐서 조금만 산책하고 금방 들어와야 할 거 같다.\n미세먼지 이래서 나가기가 싫음\n무튼 일 끝나고 집에 와서 매일 공부하기 매일 운동하기 실천 중인데 잘 됐으면 좋겠다.\n새로 산것들 많은데 빨리 해치우고 싶다.\n\u0026mdash; 끗 \u0026mdash;\n","permalink":"https://www.springboot.kr/posts/blahblah/2023-01-07/","summary":"새해 첫 주 핸드폰 보다가 알라딘에서 찜해둔 책이 들어왔길래 후다닥 가서 사 왔음.\n자바 퍼즐러\n자바 네트워크 프로그래밍\n이렇게 2개 샀는데 둘 다 절판이라 못 구하는 건데 알라딘에 들어왔길래 후다닥 가서 사 왔다.\n네트워크 프로그래밍은 수유에 가서 퍼즐로는 노원에 가서 사고 집에 왔다.\n집에 돌아오는 길에 크로켓을 팔길래 너무 먹고 싶었지만 참고 집에 와서 맛있는 걸 먹었다.\n이제 이거 쓰고 밖에 나가서 강아지 산책해야 하는데 지금 미세먼지 미쳐서 조금만 산책하고 금방 들어와야 할 거 같다.","title":"새해 첫 글"},{"content":"2022년 되돌아보기 상반기 (1~6월) 작년 5월에 대만에 가서 대략 10개월 정도를 대만에서 살다 왔다.(살 때는 몰랐는데 돌아오기 몇 달 전부터 걱정이 됐음ᄏᄏ)\n그렇게 높은 연차도 아니고 거진 1년을 쉬었다고 하니 불안한 마음이 안 들래야 안 들 수가 없었다.\n그래서 살면서 국립도서관에 출퇴근하며 공부도 하고 한국에서 챙겨간 개발 책도 읽고 시간을 안 버리려 한거 같다.\n마지막 즈음엔 평소에 잘 안 가 본 곳에 돌아다니면서 귀국하는 날을 기다렸다.\n이때가 제일 재밌었던 거 같다. 날씨도 덥지 않고 시원해서 돌아다니기가 좋았었다.\n그러면서도 평일에는 도서관에 출근 도장을 찍었다.\n주말을 제외하고 매일 출근 도장을 찍으면서 1층에 있는 카페에 들리면 말하지 않아도 아메리카노로 준비해 줬다. (가격은 한국 돈으로 2000원 정도)\n그리고 주말이 되면 와이프랑 박물관에도 가고 미술관에도 도자기도 만들러 갔다.\n그렇게 한국에 돌아오면 필요한 것들을 공부하고 준비하며 지내다 2월 말에 한국에 들어오게 되었다.\n한국에 돌아오고 나서는 일주일 정도는 쉬고 그다음부터 회사를 구하려고 여기저기 이력서를 넣기 시작했다.\n솔직히 한국에 돌아올 때 걱정이 좀 돼서 혹시 안되면 어떻게 하지 이런 생각을 조금 많이 하기도 했는데 결국엔 뭐 지금 잘 다니고 있어서 다행인 것 같다.\n그래서 3월에 회사 입사일을 정하고 4월에 출근을 하기로 하고 남은 3월을 쉬면서 지내니 입사일이 다가와 입사하게 되었다.\n입사를 하고 회사에 적응하며 잘 지내다 하반기쯤부터 안 좋은 문제들이 생겼다\u0026hellip;\n문제의 시작은 6월 1일 이명이 생긴것부터 문제가 시작됬다..\n하반기 (7~12월) 이명이 생기고 처음에 굉장히 힘들게 보냈다. 소음성 이명이었던 거 같은데 초기라 그런지 귀에서 삐\u0026mdash; 소리가 너무 심각해서 일상생활을 할 수 없을 정도였다.\n진짜 이렇게는 못 산다는 생각을 너무 많이 한거 같다. 그만큼 힘들었던 거 같은데 이것도 시간이 지나니 소리는 많이 줄었다. 근데 여전히 이명 소리는 있어서 잠잘 때 불편하다.\n그렇게 시간이 지나 이명이 익숙해질 즘에 눈에 검은 물체가 돌아다니기 시작했는데 찾아보니 비문증이라고 한다.\n처음 간 병원에서 그냥 몇 달 지나면 없어질 거라 했는데 안 없어져 다른 병원에 가서 검사를 받으니 그냥 이명처럼 평생 달고 살아야 한다고 한다.\n그래도 이명보다는 스트레스가 덜 받았는데 짜증 나긴 한다. 처음엔 날파리인 줄 알고 손을 허공에 휘저은 적도 있을 정도니 말이다.\n비문증이 생기고 나서부터는 이제 자신의 몸이 늙어감을 인정하고 운동을 하기 시작했다. (대충 10월부터 시작해서 3개월째가 돼가고 살도 많이 빠졌다.)\n그러던 도중 코로나에 걸리고 말았다. 작년엔 코로나 없는 대만에서 지내 코로나 걱정 없이 살고 한국에 와서는 그냥 안 걸리길래 안 걸릴 줄 알았는데 결국 걸리고야 말았다.\n근데 뭐 2,3일 열나고 끝이었다..(죽는 줄 알았음ㅋㅋ)\n코로나가 다 완치되고 한 달인가 있다가 눈에 문제가 또 생겼는데 언제부턴가 자려고 불 끄고 커튼치고 눈 감고 누우면 깜깜한 공간에서도 눈에서 번쩍번쩍거리는 게 보였다.\n혹시나 해서 눈을 떠봤는데 어두운 방안에 있는데도 번쩍번쩍 거리 길래 찾아보니 광시증이라고 해서 다음날 안과에 가서 레이저로 치료를 받았다. 치료를 받고 나서는 앞이 번쩍거리는 광시증은 사라졌는데 비문증은 남아있다.\n그 뒤로는 몸 생각해서 운동을 하느라 공부 쪽에 시간이 줄어들었지만 몸이 하나둘씩 이상해지는 거 보면 우선순위가 건강이 되는 게 맞는 거 같다.\n내년에는 좋은 일만 가득했으면 하는 나의 아주 큰 바람이다.\n-끘-\n","permalink":"https://www.springboot.kr/posts/blahblah/2022-12-22/","summary":"2022년 되돌아보기 상반기 (1~6월) 작년 5월에 대만에 가서 대략 10개월 정도를 대만에서 살다 왔다.(살 때는 몰랐는데 돌아오기 몇 달 전부터 걱정이 됐음ᄏᄏ)\n그렇게 높은 연차도 아니고 거진 1년을 쉬었다고 하니 불안한 마음이 안 들래야 안 들 수가 없었다.\n그래서 살면서 국립도서관에 출퇴근하며 공부도 하고 한국에서 챙겨간 개발 책도 읽고 시간을 안 버리려 한거 같다.\n마지막 즈음엔 평소에 잘 안 가 본 곳에 돌아다니면서 귀국하는 날을 기다렸다.\n이때가 제일 재밌었던 거 같다.","title":"이번 연도에 있던 일 되돌아보기 (비고. 2022년)"},{"content":"DDD START SERIES는 도메인 주도 개발 시작하기 책을 참고 하여 작성된 요약 글 입니다.\nEP 2.1 네 개의 영역 아키텍처를 설계할 떄 출현하는 전형적인 네 가지의 영역\n 표현 응용 도메인 인프라스트럭처 이미지 참조 Layered Architecture\n흐름도 표현 -\u0026gt; 응용 -\u0026gt; 도메인 -\u0026gt; 인프라스트럭처\n표현, 응용, 도메인 영역은 구현 기술을 사용한 코드를 직접 만들지 않는다.\n대신 인프라스트럭처 영역에서 제공한는 기능을 사용해서 필요한 기능을 개발한다.\nEP 2.2 계층 구조 아키텍처 게층 구조는 그 특성상 상위 계층에서 하위 계층으로의 의존만 존재하고 하위 계층은 상위 계층에 의존하지 않는다.\n예를 들어 표현 계층은 응용 계층에 의존하고 응용 계층이 도메인 계층에 의존하지만, 반대로 인프라스트럭처 계층이 도메인에 의존하거나 도메인이 응용 계층에 의존하지 않는다.\n계층 구조를 엄격하게 적용한다면 상위 계층은 바로 아래 계층에만 의존을 가져야 하지만 구현의 편리함을 위해 계층 구조를 유연하게 적용하기도 한다.\nEP 2.3 DIP (의존성 역전 원칙) DIP는 저수준 모듈이 고수준 모듈에 의존하도록 바꾼다.\nSOLID programming for Arduino: The Dependency Inversion Principle\nDIP를 적용하면 위의 그림과 같이 저수준 모듈이 고수준 모듈에 의존하게 된다. 고수준 모듈이 저수준 모듈을 사용하려면 고수준 모듈이 저수준 모듈에 의존해야 하는데, 반대로 저수준 모듈이 고수준 모듈에 의존한다고 해서 이를 DIP(Dependency Inversion Principle)라 한다.\n2.3.1 DIP 주의사항 DIP를 잘못 생각하면 단순히 인터페이스와 구현 클래스를 분리하는 정도로 받아들일 수 있다.\nDIP의 핵심은 고수준 모듈이 저수준 모듈에 의존하지 않도록 하기 위함인데 DIP를 적용한 결과 구조만 보고 저수준 모듈에서 인터페이스를 추출하는 경우가 있다.\nDIP를 적용할 때 하위 기능을 추상화한 인터페이스는 고소준 모듈 관점에서 도출한다.\n2.3.2 DIP와 아키텍처 인프라스트럭처 영역은 구현 기술을 다루는 저수준 모듈이고 응용 영역과 도메인 영역은 고수준 모듈이다.\n인프라스트럭처 계층이 가장 하단에 위치하느 계층형 구조와 달리 아키텍터에 DIP를 적용하면\n[ 인프라스트럭처 ] | | | | v | [ 응용 ] | | | v v [ 도메인 ] 다음과 같이 인프라스트럭처 영역이 응용 영역과 도메인 영역에 의존하는 구조가 된다.\n인프라스트럭처에 위치한 클래스가 도메인이나 응용 영역에 정의한 인터페이스를 상속받아 구현하는 구조가 되므로 도메인과 응용 영역에 대한 영향을 주지 않거나 최소화하면서 구현 기술을 변경하는 것이 가능하다.\nEP 2.4 도메인 영역의 주요 구성요소 Entity Value Aggregate Repository Domain Service 2.4.1 Entity And Value 도메인 모델의 엔티티는 단순히 데이터를 담고 있는 데이터 구조라기보다는 데이터와 함께 기능을 제공하는 객체이다.\n도메인 관점에서 기능을 구현하고 기능 구현을 캡슐화해서 데이터가 임의로 변경되는 것을 막는다.\n도메인 모델의 엔티티는 두 개 이상의 데이터가 개념적으로 하나인 경우 벨류 타입을 이용해서 표현할 수 있다는 것이다.\nRDBMS와 같은 관계형 DB는 벨류 타입을 제대로 표현하기 힘들다.\nEP.1dㅔ서 설명한 것처럼 벨류는 불변으로 구현할 것을 권장하며, 엔티티의 벨류 타입 데이터를 변경할 때는 객체 자체를 완전히 교체한다는 것을 의미한다.\n2.4.2 Aggregate 도메인이 복잡해질때 도메인 모델에서 전체 구조를 이해하는 데 도움이 되는 것이 Aggregate 다.\nModeling Aggregates with DDD and Entity Framework\n그림에서 보이듯이 Aggregate는 관련 객체를 하나로 묶은 군집이다.\nAggregate를 사용하면 개별 객체가 아닌 관련 객체를 묶어서 객체 군집 단위로 모델을 바라볼 수 있게 된다. 개별 객체 간의 관계가 아닌 Aggregate 간의 관계로 도메인 모델을 이해하고 구현하게 되며, 이를 통해 큰 틀에서 도메인 모델을 관리할 수 있다.\n2.4.3 Repository 물리적인 저장소에 도메인 객체를 보관하기 위한 도메인 모델이 Repository 다. Entity나 Value가 요구사항에서 도출되는 도메인 모델이라면 Repository는 구현을 위한 도메인 모델이다.\n2.5 요청 처리 흐름 표현 (Controller) → 응용 (Service) -\u0026gt; 도메인 (Danain object) -\u0026gt; 인프라스트럭처 (Repository)\n브라우저 -\u0026gt; (http) -\u0026gt; Controller -\u0026gt; Service -\u0026gt; Domain Object / Repository\nService에서 Domain 영역은 객체의 기능 Repository는 객체 리턴.\nService에서는 트랜잭션 관리를 잘해야 한다.\n2.6 인프라스트럭처 개요 인프라스트럭처는 위의 상위 계층 모두를 지원한다.\n표현, 응용, 도메인 영역..\n응용과 도메인 영역에서 구현 기술에 대한 의존은 가져가는 것이 나쁘지 않다고 생각한다.\n표현 영역은 인프라스트럭처 영역과 쌍은이룬다.\n인프라스트럭처의 영역은 다른 영역과 잘 조합해서 사용해야한다.\n우연성이 증가 될 수 있다.\n2.7 모듈 구성 아키텍처의 생각 영역은 별도 패키지에 위치한다.\n도메인이 크면 하위 도메인으로 나누고 도메인마다 별도의 패키지를 구성한다.\n도메인 모델은 애그리거트를 기준으로 다시 패키지를 구성한다.\nex) 도메인이 여러개인 경우 도메인 패키지를 나눈다.\n애그리거트, 모델, 레포지토리는 같은 영역에 위치시킨다.\nex)\ndomain.order domain.service application.product application.category 단지 예시 일뿐 정해진 규칙은 없다..\n모든 구성에 정해진 정답은 없지만. 왠만하면 잘 분리하자.\nREFERENCE 도메인 주도 개발 시작하기\n ","permalink":"https://www.springboot.kr/posts/java/start-ddd/ep2-start-ddd-model/","summary":"DDD START SERIES는 도메인 주도 개발 시작하기 책을 참고 하여 작성된 요약 글 입니다.\nEP 2.1 네 개의 영역 아키텍처를 설계할 떄 출현하는 전형적인 네 가지의 영역\n 표현 응용 도메인 인프라스트럭처 이미지 참조 Layered Architecture\n흐름도 표현 -\u0026gt; 응용 -\u0026gt; 도메인 -\u0026gt; 인프라스트럭처\n표현, 응용, 도메인 영역은 구현 기술을 사용한 코드를 직접 만들지 않는다.\n대신 인프라스트럭처 영역에서 제공한는 기능을 사용해서 필요한 기능을 개발한다.\nEP 2.2 계층 구조 아키텍처 게층 구조는 그 특성상 상위 계층에서 하위 계층으로의 의존만 존재하고 하위 계층은 상위 계층에 의존하지 않는다.","title":"[DDD START SERIES] EP.2 아키텍처 개요"},{"content":"DDD START SERIES는 도메인 주도 개발 시작하기 책을 참고 하여 작성된 요약 글 입니다.\nEP.1.1 도메인 이란? 예를 들어 개발자 입장에서 온라인 서점은 구현해야 할 소프트웨어의 대상이 될 수 있다.\n온라인 서점을 온라인으로 책을 판매하는 데 필요한 상품 조회, 구매, 결제, 배송 추적 등 기능이 필요하다.\n이때 온라인 서점은 소프트웨어로 해결하고자 하는 문제 영역, 즉 도메인에 해당한다.\n한 도메인은 다시 하위 도메인으로 나눌 수 있다.\nExample\n ---- [정산] / / [주문] ------ [회원] \\ | \\ | ---- [회원] 무조건 고정된 하위 도메인이 존재하는 것은 아니고 필요에 따라 나뉠 수 있다. (있을 수도 있고 없을 수도 있다.)\nEP.1.2 도메인 전문가와 개발자 간 지식 공유 첫 단추가 잘못 끼워지면 모든 단추가 잘못 끼워지듯이 요구사항을 올바르게 이해하지 못하면 요구하지 않은 엉뚱한 기능을 만들게 된다.\n잘못 개발한 코드를 수정하기 위해서는 많은 노력이 든다. 그러므로 요구사항을 올바르게 이해 해야한다.\n그렇기 때문에 도메인 전문가 만큼은 아니지만 이해관계자와 개발자도 도메인 지식을 갖춰야 한다.\n제품 개발과 관련된 도메인 전문가, 관계자, 개발자가 같은 지식을 공유하고 직접 소통할수록 도메인 전문가가 원하는 제품을 만들 가능성이 높아진다.\n도메인 주도 설계에서 도메인 전문가는 그만큼 중요하다.\nGarbage in, Garbage out을 기억 하라.\nEP.1.3 도메인 모델 도메인 모델을 사용하면 여러 관계자들이 동일한 모습으로 도메인을 이해하고 도메인 지식을 공유하는 데 도움이 된다.\n도메인을 이해하려면 도메인이 제공하는 기능과 도메인의 주요 데이터 구성을 파악해야 하는데 이런 면에서 기능과 데이터를 함께 보여주는 객체 모델은 도메인을 모델링하기에 적합하다.\n도메인 모델을 객체로만 모델링할 수 있는 것은 아니다.\n관계가 중요한 도메인이라면 그래프를 이용해서 도메인을 모델링할 수 있다.\n수학 공식을 활용해서 도메인 모델을 만들 수도 있다. 도메인을 이해하는 데 도움이 된다면 표현 방식이 무엇인지는 중요하지 않다.\n도메인 모델은 기본적으로 도메인 자체를 이해하기 위한 개념 모델이다.\n개념 모델과 구현 모델은 서로 다른 것이지만 구현 모델이 개념 모델을 최대한 따르도록 할 수는 있다.\nEP.1.4 도메인 모델 패턴 아기텍처 구성 (원본)\n도메인 모델 패턴은 아키텍처 구성의 도메인 계층을 객체 지향 기법으로 구현하는 패턴을 말한다.\n도메인 계층은 도메인의 핵심 규칙을 구현한다. 주문 도메인의 경우 \u0026lsquo;출고 전에 배송지를 변경할 수 있다\u0026rsquo; 라는 규칙과 \u0026lsquo;주문 취소는 배송 전에만 할 수 있다\u0026rsquo;라는 규칙을 구현한 코드가 도메인 계층에 위치하게 된다.\n이런 도메인 규칙을 객체 지향 기법으로 구현하는 패턴이 도메인 모델 패턴이다.\nEP.1.5 도메인 모델 도출 아무리 뛰어난 개발자라 할지라도 도메인에 대한 이해 없이 코딩을 시작할 수는 없다.\n기획서, 유스케이스, 사용자 스토리와 같은 요구사항과 관련자와의 대화를 통해 도메인을 이해하고 이를 바탕으로 도메인 모델 초안을 만들어야 비로소 코드를 작성할 수 있다.\n그리고 나서 만든 모델은 요구사항 정련을 위해 도메인 전문가나 다른 개발자와의 논의하는 과정에서 공유하기도 한다.\nEP.1.6 엔티티와 벨류 요구사항에서 도출한 모델은 크게 엔티티와 벨류로 구분할 수 있다.\n엔티티와 벨류를 제대로 구분해야 도메인을 올바르게 설계하고 구현할 수 있기 때문에 이 둘의 차이를 명확하게 이해하는 것은 도메인을 구현하는 데 있어 중요하다.\n1.6.1 엔티티 엔티팅의 가장큰 특징은 식별자를 가지는 것이다.\n식별자는 엔티티 객체마다 고유해서 각 엔티티는 서로 다른 식별자를 갖는다. 예를 들어 주문 도메인에서 각 주문은 주문번호를 가지고 있는데 이 주문번호는 각 주문마다 서로 다르다. 따라서 주문번호가 주문의 식별자가 된다.\n주문에서 배송지 주소가 바뀌거나 상태가 뷔꾸더라도 주문번호가 바뀌지 않는 것처럼 엔티티의 식별자는 바뀌지 않는다. 엔티티를 생성하고 속성을 바꾸고 삭제할 때까지 식별자는 유지된다.\n1.6.2 엔티티의 식별자 생성 엔티티의 식별자를 생성하는 시점은 도메인의 특징과 사용하는 기술에 따라 달라진다.\n 특정 규칙에 따라 생성 UUID나 Nano ID와 같은 고유 식별자 생성기 사용 값을 직접입력 일련번호 사용(시퀀스나 DB의 자동 증가 컬럼 사용) 1.6.3 벨류 타입 벨류 타입은 개념적으로 완전한 하나를 표현할 떄 사용한다.\n벨류 객체의 데이터를 변경할 때는 기존 데이터를 변경하기보다는 변경한 데이터를 갖고 새로운 밸류 객체를 생성하는 방식을 선호한다. (불변 객체는 참조 투명성과 스레드에 안전한 특징을 가지고 있다.)\n벨류 타입을 분현으로 구현하는 여러 이유가 있는데 가장 중요한 이유는 안전한 코드를 작성할 수 있는다는 데 있다.\n1.6.4 엔티티 식별자와 밸류 타입 도메인에서 특별한 의미를 지니는 경우가 많기 때문에 식별자를 위한 벨류 타입을 사용해서 의미가 잘 드러날 수 있도록 하자.\n1.6.5 도메인 모델에 set 메서드 넣지 않기 접근 범위를 private으로 선언한 경우 외부에서 데이터를 변경할 목적으로 set 메서드를 사용할 수 없다.\n불변 벨류 타입을 사용하면 자연스럽게 벨류 타입에는 set 메서드를 구현하지 않는다.\nset 메서드를 구현해야 할 특별한 이유가 없다면 불변 타입의 장점을 살릴 수 있도록 벨류 타입은 불변으로 구현한다.\nEP.1-7 도메인 용어와 유비쿼터스 언어 코드를 작성할 때 도메인에서 사용하는 용어는 매우 중요하다.\n도메인에서 사용하는 용어를 코드에 반영하지 않으면 그 코드는 개발자에게 코드의 의미를 해석해야 하는 부담을 준다.\n코드를 도메인 용어로 해석하거나 도메인 용어를 코드로 해석하는 과정이 줄어든다.\n이는 코드의 가독성을 높이고 코드를 분석하느고 이해하는 시간을 줄여준다.\n최대한 도메인 용어를 사용해서 도메인 규칙을 코드로 작성하게 되므로 버그또한 줄어 들게 된다.\n도메인 용어에 알맞은 단어를 찾는 시간을 아까워하지 말자.\nREFERENCE 도메인 주도 개발 시작하기\n ","permalink":"https://www.springboot.kr/posts/java/start-ddd/chp1-start-ddd-model/","summary":"DDD START SERIES는 도메인 주도 개발 시작하기 책을 참고 하여 작성된 요약 글 입니다.\nEP.1.1 도메인 이란? 예를 들어 개발자 입장에서 온라인 서점은 구현해야 할 소프트웨어의 대상이 될 수 있다.\n온라인 서점을 온라인으로 책을 판매하는 데 필요한 상품 조회, 구매, 결제, 배송 추적 등 기능이 필요하다.\n이때 온라인 서점은 소프트웨어로 해결하고자 하는 문제 영역, 즉 도메인에 해당한다.\n한 도메인은 다시 하위 도메인으로 나눌 수 있다.\nExample\n ---- [정산] / / [주문] ------ [회원] \\ | \\ | ---- [회원] 무조건 고정된 하위 도메인이 존재하는 것은 아니고 필요에 따라 나뉠 수 있다.","title":"[DDD START SERIES] EP.1 도메인 모델 시작하기"},{"content":"MDC(Mapped Diagnostic Context) MDC는 로그를 더 잘 추적할 수 있도록 하는 정보를 로그 메세지에 찍을수있게 해주는 기능을 한다.\nMap으로 되있으며 ThreadLocal 사용으로 인해 Thread 단위로 생성된다.\nSlf4j MDC를 사용 기준으로 org.slf4j.MDC 클래스 안에 static으로 선언된 MDCAdapter 를 찾아 볼 수 있다.\nMDCAdapter는 인터페이스로 되있는데 Adapter를 implements 하는 클래스를 찾아 보면 LogbackMDCAdapter 가 보인다.\n여기에 보면\npublic class LogbackMDCAdapter implements MDCAdapter { ... final ThreadLocal\u0026lt;Map\u0026lt;String, String\u0026gt;\u0026gt; copyOnThreadLocal = new ThreadLocal\u0026lt;Map\u0026lt;String, String\u0026gt;\u0026gt;(); ... } 이렇게 ThreadLocal로 선언된 걸 찾을 수 있다.\nLogback 설정하기 MDC를 사용하기 위해서는 Logback 설정이 필요하다.\n이 설정은 Spring WebFlux 기준 이다.\napplication.yml logging: config: classpath:logback/logback.xml logback.xml logback.xml에서는 MDC에서 설정한 값을 사용하려고 한다.\n설정한 값을 사용하기 위해 [%X{uuid}]라는 값을 밑의 설정 파일에 명시해 둔 것을 확인할 수 있다.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;property name=\u0026#34;LOG_DIR\u0026#34; value=\u0026#34;../\u0026#34; /\u0026gt; \u0026lt;appender name=\u0026#34;CONSOLE\u0026#34; class=\u0026#34;ch.qos.logback.core.ConsoleAppender\u0026#34;\u0026gt; \u0026lt;layout class=\u0026#34;ch.qos.logback.classic.PatternLayout\u0026#34;\u0026gt; \u0026lt;Pattern\u0026gt; [%-5level] [%thread] [%X{uuid}] %d{yyyy-MM-dd HH:mm:ss.SSS} : %30logger{5} - %msg%n \u0026lt;/Pattern\u0026gt; \u0026lt;/layout\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;appender name=\u0026#34;SYSLOG\u0026#34; class=\u0026#34;ch.qos.logback.core.rolling.RollingFileAppender\u0026#34;\u0026gt; \u0026lt;file\u0026gt;${LOG_DIR}/syslog/syslog.log\u0026lt;/file\u0026gt; \u0026lt;layout class=\u0026#34;ch.qos.logback.classic.PatternLayout\u0026#34;\u0026gt; \u0026lt;Pattern\u0026gt; [%-5level] [%thread] [%X{uuid}] %d{yyyy-MM-dd HH:mm:ss.SSS} : %30logger{5} - %msg%n \u0026lt;/Pattern\u0026gt; \u0026lt;/layout\u0026gt; \u0026lt;rollingPolicy class=\u0026#34;ch.qos.logback.core.rolling.TimeBasedRollingPolicy\u0026#34;\u0026gt; \u0026lt;fileNamePattern\u0026gt;${LOG_DIR}/syslog/syslog.%d{yyyy-MM-dd}.%i.log\u0026lt;/fileNamePattern\u0026gt; \u0026lt;timeBasedFileNamingAndTriggeringPolicy class=\u0026#34;ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\u0026#34;\u0026gt; \u0026lt;maxFileSize\u0026gt;10MB\u0026lt;/maxFileSize\u0026gt; \u0026lt;/timeBasedFileNamingAndTriggeringPolicy\u0026gt; \u0026lt;/rollingPolicy\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;appender name=\u0026#34;ACCESSLOG\u0026#34; class=\u0026#34;ch.qos.logback.core.rolling.RollingFileAppender\u0026#34;\u0026gt; \u0026lt;file\u0026gt;${LOG_DIR}/accesslog/accesslog.log\u0026lt;/file\u0026gt; \u0026lt;layout class=\u0026#34;ch.qos.logback.classic.PatternLayout\u0026#34;\u0026gt; \u0026lt;Pattern\u0026gt; [%thread] [%X{uuid}] %msg%n \u0026lt;/Pattern\u0026gt; \u0026lt;/layout\u0026gt; \u0026lt;rollingPolicy class=\u0026#34;ch.qos.logback.core.rolling.TimeBasedRollingPolicy\u0026#34;\u0026gt; \u0026lt;fileNamePattern\u0026gt;${LOG_DIR}/accesslog/accesslog.%d{yyyy-MM-dd}.%i.log\u0026lt;/fileNamePattern\u0026gt; \u0026lt;timeBasedFileNamingAndTriggeringPolicy class=\u0026#34;ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\u0026#34;\u0026gt; \u0026lt;maxFileSize\u0026gt;10MB\u0026lt;/maxFileSize\u0026gt; \u0026lt;/timeBasedFileNamingAndTriggeringPolicy\u0026gt; \u0026lt;/rollingPolicy\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;root level=\u0026#34;info\u0026#34;\u0026gt; \u0026lt;appender-ref ref=\u0026#34;CONSOLE\u0026#34; /\u0026gt; \u0026lt;appender-ref ref=\u0026#34;SYSLOG\u0026#34; /\u0026gt; \u0026lt;/root\u0026gt; \u0026lt;logger name=\u0026#34;kr.springboot.mdc\u0026#34; level=\u0026#34;debug\u0026#34; additivity=\u0026#34;false\u0026#34;\u0026gt; \u0026lt;appender-ref ref=\u0026#34;CONSOLE\u0026#34; /\u0026gt; \u0026lt;/logger\u0026gt; \u0026lt;logger name=\u0026#34;access-log\u0026#34; level=\u0026#34;info\u0026#34; additivity=\u0026#34;false\u0026#34;\u0026gt; \u0026lt;appender-ref ref=\u0026#34;ACCESSLOG\u0026#34; /\u0026gt; \u0026lt;/logger\u0026gt; \u0026lt;/configuration\u0026gt; Spring WebFlux MDC 설정 하기 WebFilter 필터에서는 들어오는 요청의 쓰레드에 MDC를 설정할 수 있다.\npackage kr.springboot.mdc.filter; import org.slf4j.MDC; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import reactor.core.publisher.Mono; import reactor.util.context.Context; import java.util.UUID; public class MdcLoggingFilter implements WebFilter { private static final String MDC_UUID = \u0026#34;uuid\u0026#34;; @Override public Mono\u0026lt;Void\u0026gt; filter(ServerWebExchange exchange, WebFilterChain chain) { return chain.filter(exchange).contextWrite(context -\u0026gt; { final String uuid = UUID.randomUUID().toString(); MDC.put(MDC_UUID, uuid); return Context.of(MDC_UUID, uuid); }); } } @Configuration 여기서 MDC에서 설정 한 값을들 초기화 하기 위해 copyToMdc Method에서 Clear를 하는 것을 볼 수 있다.\npackage kr.springboot.mdc.config; import kr.springboot.mdc.filter.MdcLoggingFilter; import lombok.RequiredArgsConstructor; import org.reactivestreams.Subscription; import org.slf4j.MDC; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import reactor.core.CoreSubscriber; import reactor.core.publisher.Hooks; import reactor.core.publisher.Operators; import reactor.util.context.Context; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.Map; import java.util.stream.Collectors; /** * @link https://www.novatec-gmbh.de/en/blog/how-can-the-mdc-context-be-used-in-the-reactive-spring-applications/ */ @Configuration public class MdcContextLifterConfiguration { public static final String MDC_CONTEXT_REACTOR_KEY = MdcContextLifterConfiguration.class.getName(); @PostConstruct @SuppressWarnings(\u0026#34;unchecked\u0026#34;) public void contextOperatorHook() { Hooks.onEachOperator(MDC_CONTEXT_REACTOR_KEY, Operators.lift((scannable, subscriber) -\u0026gt; new MdcContextLifter(subscriber))); } @Bean public MdcLoggingFilter mdcLoggingFilter() { return new MdcLoggingFilter(); } @PreDestroy public void cleanupHook() { Hooks.resetOnEachOperator(MDC_CONTEXT_REACTOR_KEY); } /** * Helper that copies the state of Reactor [Context] to MDC on the #onNext function. */ @RequiredArgsConstructor public static class MdcContextLifter\u0026lt;T\u0026gt; implements CoreSubscriber\u0026lt;T\u0026gt; { private final CoreSubscriber\u0026lt;T\u0026gt; coreSubscriber; @Override public void onSubscribe(Subscription subscription) { coreSubscriber.onSubscribe(subscription); } @Override public void onNext(T t) { copyToMdc(coreSubscriber.currentContext()); coreSubscriber.onNext(t); } @Override public void onError(Throwable throwable) { coreSubscriber.onError(throwable); } @Override public void onComplete() { coreSubscriber.onComplete(); } @Override public Context currentContext() { return coreSubscriber.currentContext(); } /** * Extension function for the Reactor [Context]. Copies the current context to the MDC, if context is empty clears the MDC. * State of the MDC after calling this method should be same as Reactor [Context] state. * One thread-local access only. */ void copyToMdc(Context context) { if (context != null \u0026amp;\u0026amp; !context.isEmpty()) { Map\u0026lt;String, String\u0026gt; map = context.stream() .collect(Collectors.toMap(e -\u0026gt; e.getKey().toString(), e -\u0026gt; e.getValue().toString())); MDC.setContextMap(map); } else { MDC.clear(); } } } } 확인하기 모든 설정이 끝났다면 한번 확인해보자.\n예제는 http 요청을 하는 예제로 설정 전 로그는 다음과 같다.\n전 2022-09-04 19:57:30.184 INFO 80170 --- [ctor-http-nio-3] k.springboot.mdc.service.MdcTestService : MonoLiftFuseable 2022-09-04 19:57:30.597 DEBUG 80170 --- [ctor-http-nio-3] r.netty.http.client.HttpClientConnect : [ec54d609-1, L:/192.168.0.102:56393 - R:jsonplaceholder.typicode.com/104.21.4.48:443] Handler is being applied: {uri=https://jsonplaceholder.typicode.com/todos/1, method=GET} 2022-09-04 19:57:30.668 DEBUG 80170 --- [ctor-http-nio-3] r.n.http.client.HttpClientOperations : [ec54d609-1, L:/192.168.0.102:56393 - R:jsonplaceholder.typicode.com/104.21.4.48:443] Received response (auto-read:false) : [Date=Sun, 04 Sep 2022 10:57:32 GMT, Content-Type=application/json; charset=utf-8, Transfer-Encoding=chunked, Connection=keep-alive, X-Powered-By=Express, X-Ratelimit-Limit=1000, X-Ratelimit-Remaining=998, X-Ratelimit-Reset=1661768056, Vary=Origin, Accept-Encoding, Access-Control-Allow-Credentials=true, Cache-Control=max-age=43200, Pragma=no-cache, Expires=-1, X-Content-Type-Options=nosniff, Etag=W/\u0026#34;53-hfEnumeNh6YirfjyjaujcOPPT+s\u0026#34;, Via=1.1 vegur, CF-Cache-Status=HIT, Age=212, Report-To={\u0026#34;endpoints\u0026#34;:[{\u0026#34;url\u0026#34;:\u0026#34;https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=VHZ%2FwY%2BQiIfRRLg5C3uCqwdSSrFXtNQxmXjyt5H%2BXaHYGdJvYCILHF93IOQ3WZ47SO1oQ3Jprb9%2B5wkSD2qRBiX9%2BugQ8LmGFBFtFph%2BVsnPM1XYeH%2Bn04NhHvGLh8z5LDoazC1MrjROk1h5suPb\u0026#34;}],\u0026#34;group\u0026#34;:\u0026#34;cf-nel\u0026#34;,\u0026#34;max_age\u0026#34;:604800}, NEL={\u0026#34;success_fraction\u0026#34;:0,\u0026#34;report_to\u0026#34;:\u0026#34;cf-nel\u0026#34;,\u0026#34;max_age\u0026#34;:604800}, Server=cloudflare, CF-RAY=74563473a915b46a-HKG, alt-svc=h3=\u0026#34;:443\u0026#34;; ma=86400, h3-29=\u0026#34;:443\u0026#34;; ma=86400] 2022-09-04 19:57:30.691 DEBUG 80170 --- [ctor-http-nio-3] r.n.http.client.HttpClientOperations : [ec54d609-1, L:/192.168.0.102:56393 - R:jsonplaceholder.typicode.com/104.21.4.48:443] Received last HTTP packet 후 [INFO ] [reactor-http-nio-3] [4e3955e6-5206-4def-ab71-92e71e36d490] 2022-09-04 19:59:39.895 : k.s.m.s.MdcTestService - MonoLiftFuseable [DEBUG] [reactor-http-nio-3] [4e3955e6-5206-4def-ab71-92e71e36d490] 2022-09-04 19:59:40.771 : r.n.h.c.HttpClientConnect - [ef905c50-1, L:/192.168.0.102:56408 - R:jsonplaceholder.typicode.com/104.21.4.48:443] Handler is being applied: {uri=https://jsonplaceholder.typicode.com/todos/1, method=GET} [DEBUG] [reactor-http-nio-3] [4e3955e6-5206-4def-ab71-92e71e36d490] 2022-09-04 19:59:40.866 : r.n.h.c.HttpClientOperations - [ef905c50-1, L:/192.168.0.102:56408 - R:jsonplaceholder.typicode.com/104.21.4.48:443] Received response (auto-read:false) : [Date=Sun, 04 Sep 2022 10:59:42 GMT, Content-Type=application/json; charset=utf-8, Transfer-Encoding=chunked, Connection=keep-alive, X-Powered-By=Express, X-Ratelimit-Limit=1000, X-Ratelimit-Remaining=998, X-Ratelimit-Reset=1661768056, Vary=Origin, Accept-Encoding, Access-Control-Allow-Credentials=true, Cache-Control=max-age=43200, Pragma=no-cache, Expires=-1, X-Content-Type-Options=nosniff, Etag=W/\u0026#34;53-hfEnumeNh6YirfjyjaujcOPPT+s\u0026#34;, Via=1.1 vegur, CF-Cache-Status=HIT, Age=342, Report-To={\u0026#34;endpoints\u0026#34;:[{\u0026#34;url\u0026#34;:\u0026#34;https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=sUgBVCk8BU2qNiYJR7r7Dnf%2FlH87NAt8NWnHDj2KgiVFL%2Fc6OMFFGUhJnEy5vBize%2F%2BMLuwJIkGpKmHrRz%2Bs%2FE%2B22b6wilr6XFjQyTiFNdzWHQSVVgQYG1asn0oXHOlsqdHwTzQBzX7%2BT6vp9rUF\u0026#34;}],\u0026#34;group\u0026#34;:\u0026#34;cf-nel\u0026#34;,\u0026#34;max_age\u0026#34;:604800}, NEL={\u0026#34;success_fraction\u0026#34;:0,\u0026#34;report_to\u0026#34;:\u0026#34;cf-nel\u0026#34;,\u0026#34;max_age\u0026#34;:604800}, Server=cloudflare, CF-RAY=745637a159013d76-HKG, alt-svc=h3=\u0026#34;:443\u0026#34;; ma=86400, h3-29=\u0026#34;:443\u0026#34;; ma=86400] [DEBUG] [reactor-http-nio-3] [4e3955e6-5206-4def-ab71-92e71e36d490] 2022-09-04 19:59:40.913 : r.n.h.c.HttpClientOperations - [ef905c50-1, L:/192.168.0.102:56408 - R:jsonplaceholder.typicode.com/104.21.4.48:443] Received last HTTP packet 설정한 대로 쓰레드 옆에 UUID의 값이 찍히는 것을 확인할 수 있다.\nUUID를 따라가면 로그를 확인하기 쉬워진다.\nREFERENCE https://www.novatec-gmbh.de/en/blog/how-can-the-mdc-context-be-used-in-the-reactive-spring-applications/\n","permalink":"https://www.springboot.kr/posts/spring/webflux-and-mdc/","summary":"MDC(Mapped Diagnostic Context) MDC는 로그를 더 잘 추적할 수 있도록 하는 정보를 로그 메세지에 찍을수있게 해주는 기능을 한다.\nMap으로 되있으며 ThreadLocal 사용으로 인해 Thread 단위로 생성된다.\nSlf4j MDC를 사용 기준으로 org.slf4j.MDC 클래스 안에 static으로 선언된 MDCAdapter 를 찾아 볼 수 있다.\nMDCAdapter는 인터페이스로 되있는데 Adapter를 implements 하는 클래스를 찾아 보면 LogbackMDCAdapter 가 보인다.\n여기에 보면\npublic class LogbackMDCAdapter implements MDCAdapter { ... final ThreadLocal\u0026lt;Map\u0026lt;String, String\u0026gt;\u0026gt; copyOnThreadLocal = new ThreadLocal\u0026lt;Map\u0026lt;String, String\u0026gt;\u0026gt;(); .","title":"MDC Logback"},{"content":"이명에 걸렸다.\n소리가 난다.\n24시간 난다.\n아무것도 못한다.\n소리가 난다.\n망했다.\n이명에 걸렸다.\n","permalink":"https://www.springboot.kr/posts/blahblah/2022-06-09/","summary":"이명에 걸렸다.\n소리가 난다.\n24시간 난다.\n아무것도 못한다.\n소리가 난다.\n망했다.\n이명에 걸렸다.","title":"이명에 걸렸다."},{"content":"지금은 5월2일 12시 40분.\n이번주 띵가띵가 놀면서 생각해봄.\n개념공부 -\u0026gt; 블로그 글 작성\n코드공부 -\u0026gt; 깃허브에 커밋\n개념적인 공부와 코드 병행\n히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히\n다음주도 화이팅. 적응되가서 그렇게 피곤하지가 않다.\n공부 시작하자.\n","permalink":"https://www.springboot.kr/posts/blahblah/2022-05-02/","summary":"지금은 5월2일 12시 40분.\n이번주 띵가띵가 놀면서 생각해봄.\n개념공부 -\u0026gt; 블로그 글 작성\n코드공부 -\u0026gt; 깃허브에 커밋\n개념적인 공부와 코드 병행\n히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히\n다음주도 화이팅. 적응되가서 그렇게 피곤하지가 않다.\n공부 시작하자.","title":"띵가띵가"},{"content":"블러킹(Blocking) 블러킹은 다른 메서드를 호출할 때 제어권도 다 넘겨주고 작업이 끝난 후에 돌려 받는 것을 말한다.\n예제는 2개의 클래스로 진행되며 Data, Main으로 진행 된다.\n블러킹 예제 DataSync Class\npublic class DataSync { private int id; private long simulationDuration; public DataSync(int id, long simulationDuration) { this.id = id; this.simulationDuration = simulationDuration; } public String get() { try { Thread.sleep(this.simulationDuration); } catch (InterruptedException e) { e.printStackTrace(); } return \u0026#34;data-\u0026#34; + id; } } MainSync Class\npublic class MainSync { public static void main(String[] args) { long startTime, endTime; DataSync dataSync1 = new DataSync(1, 5000); // 5s DataSync dataSync2 = new DataSync(2, 2000); // 2s DataSync dataSync3 = new DataSync(3, 3000); // 3s startTime = System.currentTimeMillis(); System.out.println(\u0026#34;Start\u0026#34;); String d1 = dataSync1.get(); printData(d1); String d2 = dataSync2.get(); printData(d2); String d3 = dataSync3.get(); printData(d3); endTime = System.currentTimeMillis(); System.out.println(\u0026#34;Done\u0026#34;); System.out.print(\u0026#34;Execution time (ms): \u0026#34; + (endTime - startTime)); } private static void printData(Object data) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(\u0026#34;Synchronously printing \u0026#34; + data); } } 이 예제의 출력값을 보게되면 순차로 출력이 된걸 볼 수 있다.\nStart Synchronously printing data-1 Synchronously printing data-2 Synchronously printing data-3 Done Execution time (ms): 13046 이런 방식을 블러킹 방식이라 한다.\n넌블러킹(Non-Blocking) 블러킹과 다르게 제어권을 계속 가지고 있기 때문에 작업의 완료여부와 상관없이 다른 작업을 수행할 수 있는것을 말한다.\n예제는 2개의 클래스로 진행되며 Data, Main으로 진행 된다.\n넌블러킹 예제 DataASync Class\npublic class DataAsync implements Supplier\u0026lt;String\u0026gt; { private int id; private long simulationDuration; public DataAsync(int id, long simulationDuration) { this.id = id; this.simulationDuration = simulationDuration; } @Override public String get() { try { Thread.sleep(this.simulationDuration); } catch (InterruptedException e) { e.printStackTrace(); } return \u0026#34;data-\u0026#34; + id; } } MainAsync Class\npublic class MainAsync { public static void main(String[] args) { long startTime, endTime; CountDownLatch latch = new CountDownLatch(3); DataAsync dataAsync1 = new DataAsync(1, 5000); // 5s DataAsync dataAsync2 = new DataAsync(2, 2000); // 2s DataAsync dataAsync3 = new DataAsync(3, 3000); // 3s startTime = System.currentTimeMillis(); System.out.println(\u0026#34;Start\u0026#34;); try { CompletableFuture.supplyAsync(dataAsync1).thenAccept(d1 -\u0026gt; { printData(d1); latch.countDown(); }); CompletableFuture.supplyAsync(dataAsync2).thenAccept(d2 -\u0026gt; { printData(d2); latch.countDown(); }); CompletableFuture.supplyAsync(dataAsync3).thenAccept(d3 -\u0026gt; { printData(d3); latch.countDown(); }); latch.await(); } catch (Exception e) { e.printStackTrace(); } endTime = System.currentTimeMillis(); System.out.println(\u0026#34;Done\u0026#34;); System.out.print(\u0026#34;Execution time (ms): \u0026#34; + (endTime - startTime)); } private static void printData(String data) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(\u0026#34;Synchronously printing \u0026#34; + data); } } 넌블러킹의 실행된 예제의 출력값을 확인하면 위의 블러킹 방식과는 다르게 순차적으로 진행되지 않고 먼저 실행이 끝나는 순으로 출력된 모습을 볼 수 있다.\nStart Synchronously printing data-2 Synchronously printing data-3 Synchronously printing data-1 Done Execution time (ms): 6015 이런 방식을 넌블러킹이라한다.\n참조 java-selector-is-asynchronous-or-non-blocking-architecture lamngockhuong/java-blocking-vs-no-blocking-example - 끗 - 500미터\n ","permalink":"https://www.springboot.kr/posts/etc/blocking-n-non-blocking/","summary":"블러킹(Blocking) 블러킹은 다른 메서드를 호출할 때 제어권도 다 넘겨주고 작업이 끝난 후에 돌려 받는 것을 말한다.\n예제는 2개의 클래스로 진행되며 Data, Main으로 진행 된다.\n블러킹 예제 DataSync Class\npublic class DataSync { private int id; private long simulationDuration; public DataSync(int id, long simulationDuration) { this.id = id; this.simulationDuration = simulationDuration; } public String get() { try { Thread.sleep(this.simulationDuration); } catch (InterruptedException e) { e.","title":"블러킹. 넌블러킹."},{"content":" Java와 Kotlin에서 retainAll 사용하기 What is retainAll Method? retainAll 메서드는 무엇일까?\nRetainAll() 메서드는 지정된 컬렉션에 포함되지 않은 모든 배열 목록 요소를 제거하거나 메서드에 매개 변수로 전달된 컬렉션 목록의 모든 요소와 일치하는 현재 컬렉션 인스턴스의 모든 일치 요소를 유지하는 데 사용되는 메서드이다.\n아래의 그림을 보면 이해가 더 쉽다.\nretain 은 a와 b의 공통된 값만을 제외한 모든 값은 없앨 수 있다.\n그래서 어떻게 쓰는 건데? a, b라는 컬렉션 인스턴스가 존재할 때 a.retainAll(b) 이런 식으로 사용하면 된다.\n코틀린 코드를 예제로 봐보자.\nfun main(args: Array\u0026lt;String\u0026gt;) { val listExampleIntArrayString = listRetainAllExample(intArrayOf(1, 2, 2, 1), intArrayOf(2, 2)).map { it.toString() }.toTypedArray() .contentToString() println(\u0026#34;list example int array =\u0026gt; \u0026#34; + listExampleIntArrayString) val setExampleIntArrayString = setRetainAllExample(intArrayOf(1, 2, 2, 1), intArrayOf(2, 2)).map { it.toString() }.toTypedArray() .contentToString() println(\u0026#34;set example int array =\u0026gt; \u0026#34; + setExampleIntArrayString) } // Runtime 461 ms Memory 43.1 MB fun listRetainAllExample(nums1: IntArray, nums2: IntArray): IntArray { val nums1List = nums1.distinct().toMutableList() val nums2List = nums2.distinct().toMutableList() nums1List.retainAll(nums2List) return nums1List.toIntArray() } // Runtime 363 ms Memory 42.4 MB fun setRetainAllExample(nums1: IntArray, nums2: IntArray): IntArray { var nums1Set = nums1.toMutableSet() val nums2Set = nums2.toMutableSet() nums1Set.retainAll(nums2Set) return nums1Set.toIntArray() } 위의 코드를 보게 되면 [1, 2, 2, 1], [2, 2]를 파라미터로 받아서 중복이 제거된 값들을 추려 IntArray로 다시 반환하는 예제이다.\n예제의 코드를 보면 컬렉션 인스턴스인 List와 Set으로 예제를 든다.\n단 여기서 List를 통해 retainAll을 할 때 중복 제거를 원하는 경우 distinct()를 사용해 중복제거를 한번 해주고 리스트로 변경 후 retainAll을 실행하였다.\n중복이 제거되지 않았을 경우의 결괏값은 다음과 같다.\nlist example int array =\u0026gt; [2, 2] set example int array =\u0026gt; [2] distinct()를 사용해 중복 제거를 한 결괏값은 다음과 같다.\nlist example int array =\u0026gt; [2] set example int array =\u0026gt; [2] 자바에서 List로 retainAll을 사용한 코드 예제\npublic class AboutRetainAll { public static void main(String[] args) { System.out.println(retainAll(new int[]{1, 2, 2, 1}, new int[]{2, 2})); } public static int[] retainAll(int[] nums1, int[] nums2) { Set\u0026lt;Integer\u0026gt; nums1Set = Arrays.stream(nums1).boxed().collect(Collectors.toSet()); Set\u0026lt;Integer\u0026gt; nums2Set = Arrays.stream(nums2).boxed().collect(Collectors.toSet()); nums1Set.retainAll(nums2Set); return nums1Set.stream().mapToInt(x -\u0026gt; x).toArray(); } } END 이렇게 해서 retainAll 메서드에 알아봤다. 자주 써먹을 수 있길 😁\nReference 【DataStructure】The difference among methods addAll(),retainAll() and removeAll() kotlin-stdlib / kotlin.collections / retainAll retainAll-java.util.Collection- ","permalink":"https://www.springboot.kr/posts/java/about-retain-all/","summary":"Java와 Kotlin에서 retainAll 사용하기 What is retainAll Method? retainAll 메서드는 무엇일까?\nRetainAll() 메서드는 지정된 컬렉션에 포함되지 않은 모든 배열 목록 요소를 제거하거나 메서드에 매개 변수로 전달된 컬렉션 목록의 모든 요소와 일치하는 현재 컬렉션 인스턴스의 모든 일치 요소를 유지하는 데 사용되는 메서드이다.\n아래의 그림을 보면 이해가 더 쉽다.\nretain 은 a와 b의 공통된 값만을 제외한 모든 값은 없앨 수 있다.\n그래서 어떻게 쓰는 건데? a, b라는 컬렉션 인스턴스가 존재할 때 a.","title":"Java와 Kotlin에서 retainAll 사용하기"},{"content":"(대만 이란쪽 좋은 곳)\n벌써 3번째 개발일지 3일 전인가 쓰고 안 썼는데 오늘 다음 일지를 작성하게 됐다.\n우선 2번째 일지 후 있었던 일들은 인증 쪽에 구글 메일을 통해 메일을 인증하는 기능이 추가됐는데 일반 사용자를 위한 목적도 있지만 게시물을 올려주는 메인 사용자들을 위한 검증을 위해 추가했다.\n그리고 전에 생각했던 코틀린 은 안 하기로 했다. 코틀린 은 안 하고 그냥 전부 자바로 하기로 했는데 그냥 하나로 쭉 하는 게 좋을 거 같아서 그렇게 하기로 해서 코틀린 은 다음 기회에..\n그리고 프론트쪽을 해보려다가 그만 참지 못하고 꺼버렸다. 플러터로 해봐야겠다. 자바스크립트랑은 스타일이 좀 달라서 전에 해봤을 때 그렇게 지루하진 않았는데 또 하다 보면 혹시 모르겠지만.\n그리고 전에 말하던 DB, Kafka, Logstash를 작업용 피시에서 때 버렸다. AWS Lightsail로 옮겨버렸는데 1코어 1기가 5천 원짜리다. 3개월 무료라고 해서 혜자인듯하다 처음에 다 세팅해서 작동시키니 카프카에서 메모리 아웃 에러가 났다. 그래서 Memory swap 걸고 3기 가까지 늘렸다. 그랬더니 swap 걸린 메모리를 마구 쓴다.\n이제 작업용 컴퓨터에서는 서버 개발만 하게 됐는데. 이것도 분리하고 싶은데 내가 주로 집에서 작업하지 않고 도서관이나 카페에서 작업하다 보니 디스커버리에 연결할 수 있는 외부 포트 여는 게 쉽지 않다. 공공 와이파이나 테더링을 해서 하면 포트를 열 수가 없다. 그래서 그냥 다 한 컴퓨터에서 돌리고 있다.\n그래서 이제 남은 건 유저 서비스에 후원 기능을 넣어주는 거랑 포스트 서비스랑 미디어 서비스 개발이 남았다.\n포스트 서비스에서는 게시물을 올리고 댓글 정도? 관리해 주는 서비스이고 미디어는 말 그대로 미디어 서비스인데 미디어 서비스를 S3나 Object storage에 올릴까 생각했는데 그냥 가격 때문에 안 하기로 했다. 이건 사비로 운영 예정이기 때문임..\n뭐 남은 서비스들을 개발하면서 feign으로 묶어줄 API들이 있으면 더 개발하게 될지도 모르지만 우선 지금 선에서는 인증 쪽과 유저는 마무리가 돼가는 느낌이다.\n- 끗 - (대만 사는곳 옥상 하늘)\n","permalink":"https://www.springboot.kr/posts/devnote/thecarbtoon-3/","summary":"(대만 이란쪽 좋은 곳)\n벌써 3번째 개발일지 3일 전인가 쓰고 안 썼는데 오늘 다음 일지를 작성하게 됐다.\n우선 2번째 일지 후 있었던 일들은 인증 쪽에 구글 메일을 통해 메일을 인증하는 기능이 추가됐는데 일반 사용자를 위한 목적도 있지만 게시물을 올려주는 메인 사용자들을 위한 검증을 위해 추가했다.\n그리고 전에 생각했던 코틀린 은 안 하기로 했다. 코틀린 은 안 하고 그냥 전부 자바로 하기로 했는데 그냥 하나로 쭉 하는 게 좋을 거 같아서 그렇게 하기로 해서 코틀린 은 다음 기회에.","title":"TheCarbtoon 개발일지 - 3"},{"content":"(지내고 있는 곳의 가까운 학교 운동장?)\n한.. 10일 전쯤 개발일지 1을 올렸는데 그 뒤로 몸이 좀 안 좋아서 일주일 쉰 거 같다.\n지금 진행 상황은 모든 서비스들끼리 jwt를 통해 사용자를 확인할 수 있게 됐고 인증 서비스와 유저 서비스의 개발이 얼추 진행돼가고 있는데. 요즘 드는 생각이 아무나 그림을 올릴 수 있게 만들고 카카오 페이로 후원을 받을 수 있는 기능을 구현하고 싶다.\n재미로 그리는 사람이더라도 후원을 받는 건 나쁘지 않다고 생각한다. 자신의 결과물에 산물이랄까\n아무튼 아직 생각 중인 기능이기 때문에 추후에 추가될 수도 있고 안될 수도 있다.\n그리고 인증 서비스와 유저 서비스의 유저는 서로 공유한다. 단 DB는 다름. 인증은 몽고로 유저는 mysql로 되어있다.\n그리고 인증 서비스에서는 회원가입, 토큰 발급, 리프레시 토큰 발급 정도?의 역할을 하고 있고 나머지 유저와 관련된 API들은 유저 서비스에서 맡아서 하고 있게 개발 중이다.\n그래서 서로 feign을 사용해 서로 바뀌는 값들에 대해 업데이트가 될 수 있게 만들어 놓긴 했는데. 나중에 k8s를 넣으면 어떻게 될지 생각도 좀 해봐야겠다.\n우선 현재 진행 상황은 이러하고 웬만하면 빠른 시간 내에 빡세개해서 일찍 끝내고 싶다.\n프론트도 해야 하는데 jquery로 할지 svelte를 할지 고민 중이다..\n프론트에는 소질이 없는 것 같다. 재미가 없어..\n그리고 백엔드가 끝나면 사용자들도 모집해야 다 메일 보내고 쪽지 보내고 해야 해서 바쁠 것 같다.\n- 끗 - (대만의 타이페이 시먼에 있는 돈키호테에서)\n","permalink":"https://www.springboot.kr/posts/devnote/thecarbtoon-2/","summary":"(지내고 있는 곳의 가까운 학교 운동장?)\n한.. 10일 전쯤 개발일지 1을 올렸는데 그 뒤로 몸이 좀 안 좋아서 일주일 쉰 거 같다.\n지금 진행 상황은 모든 서비스들끼리 jwt를 통해 사용자를 확인할 수 있게 됐고 인증 서비스와 유저 서비스의 개발이 얼추 진행돼가고 있는데. 요즘 드는 생각이 아무나 그림을 올릴 수 있게 만들고 카카오 페이로 후원을 받을 수 있는 기능을 구현하고 싶다.\n재미로 그리는 사람이더라도 후원을 받는 건 나쁘지 않다고 생각한다. 자신의 결과물에 산물이랄까","title":"TheCarbtoon 개발일지 - 2"},{"content":"Spring Cloud Stream Binder Kafka를 위한 글.\n버전이 올라오면서 @OUTPUT 어노테이션이 Deprecated 돼서 찾아봤는데 StreamBridge를 이용하면 된다 한다.\n일단 처음부터 봐봅시다.\nimplementation group: \u0026#39;org.springframework.cloud\u0026#39;, name: \u0026#39;spring-cloud-stream-binder-kafka\u0026#39;, version: \u0026#39;3.2.1\u0026#39; 를 추가하면 5개의 라이브러리가 추가 된다.\nGradle: org.springframework.cloud:spring-cloud-function-context:3.2.1 Gradle: org.springframework.cloud:spring-cloud-function-core:3.2.1 Gradle: org.springframework.cloud:spring-cloud-stream:3.2.1 Gradle: org.springframework.cloud:spring-cloud-stream-binder-kafka:3.2.1 Gradle: org.springframework.cloud:spring-cloud-stream-binder-kafka-core:3.2.1 그럼 시작해보자.\n우선 application.yml에 cloud kafka를 설정해줍시다.\ncloud: function: definition: consumer;producer stream: kafka: bindings: producer-out-0: producer: configuration: key: serializer: org.apache.kafka.common.serialization.StringSerializer consumer-in-0: consumer: configuration: key: deserializer: org.apache.kafka.common.serialization.StringDeserializer value: deserializer: com.example.studyspringboot.cloud.stream.binder.kafka.MessageDeserializer # (2) binder: brokers: localhost:9094 bindings: producer-out-0: # (1) destination: carbtoon.test.service # topic contentType: application/json consumer-in-0: destination: carbtoon.test.service # topic consumer: use-native-decoding: true # (3) 커스텀 deserializer를 사용하기 위해 활성화를 위해 true로 설정합니다. 설정이 끝난다면 Producer를 위한 클래스를 하나 작성.\n @RequiredArgsConstructor @Component public class KafkaProducer { private final StreamBridge streamBridge; // StreamBridge를 주입해주자. @Async public void send(SpringbootKRPayload payload) { streamBridge.send(\u0026#34;producer-out-0\u0026#34;, MessageBuilder // ↑ application.yml의 (1)과 같아야합니다. // send의 첫번째 String parameter 이름은 String bindingName 입니다. .withPayload(payload) .setHeader(KafkaHeaders.MESSAGE_KEY, UUID.randomUUID().toString()) .build()); } } 이렇게 Producer가 생성이 끝나면 다음으로는 Consumer 클래스를 생성.\n @Component public class KafkaConsumer { @Bean public Consumer\u0026lt;Message\u0026gt; consumer() { return message -\u0026gt; System.out.println(message); } } 그리고 테스트를위한 Payload를 하나 만듭니다.\n @Getter @Setter @ToString public class SpringbootKRPayload { private String host; private String title; private String name; @JsonProperty(\u0026#34;payload_type\u0026#34;) private PayloadType payloadType; @Builder public SpringbootKRPayload(String host, String title, String name, PayloadType payloadType) { this.host = host; this.title = title; this.name = name; this.payloadType = payloadType; } } public enum PayloadType { CREATED, UPDATED, DELETED } 그리고 application.yml의 (2)에 필요한 커스텀 deserializer 클래스를 하나 만듭니다.\npublic class MessageDeserializer implements Deserializer\u0026lt;SpringbootKRPayloadDeserializer\u0026gt; { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public SpringbootKRPayloadDeserializer deserialize(String topic, byte[] data) { try { return objectMapper.readValue(new String(data), SpringbootKRPayloadDeserializer.class); } catch (IOException e) { throw new SerializationException(e); } } } 위의 클래스를 사용하기 위해서는 application.yml의 (3)을 true로 설정해 주어야합니다.\n그리고 다음으로 위의 코드중 Deserializer를 위한 SpringbootKRPayloadDeserializer를 만들어 줍니다.\npublic class SpringbootKRPayloadDeserializer { @JsonProperty(\u0026#34;payload_type\u0026#34;) private String payloadType; @JsonProperty(\u0026#34;host\u0026#34;) private String host; @JsonProperty(\u0026#34;name\u0026#34;) private String name; @JsonProperty(\u0026#34;title\u0026#34;) private String title; } 그리고 테스트를 위한 스케줄러를 하나 만들어 봅니다. (스케줄러를 만들었으니 application에 @EnableScheduling 추가)\n @RequiredArgsConstructor @Component public class Scheduler { private KafkaProducer kafkaProducer; @Scheduled(cron = \u0026#34;*/5 * * * * *\u0026#34;) public void sendMessage() { kafkaProducer.send(SpringbootKRPayload.builder() .host(\u0026#34;SPRINGBOOT.KR\u0026#34;) .title(\u0026#34;SPRING CLOUD STREAM KAFKA\u0026#34;) .name(\u0026#34;JINWOO-LEE\u0026#34;) .payloadType(PayloadType.CREATED) .build()); } } 이제 실행하면??\nGenericMessage [payload=SpringbootKRPayloadDeserializer( payloadType=CREATED, host=SPRINGBOOT.KR, name=JINWOO-LEE, title=SPRING CLOUD STREAM KAFKA), headers={deliveryAttempt=1, kafka_timestampType=CREATE_TIME, kafka_receivedTopic=carbtoon.test.service, skip-input-type-conversion=true, kafka_offset=158, scst_nativeHeadersPresent=true, kafka_consumer=org.apache.kafka.clients.consumer.KafkaConsumer@15d20e35, source-type=streamBridge, id=6a8cd101-3ac7-59ef-3d54-0429e761a4e8, kafka_receivedPartitionId=0, contentType=application/json, kafka_receivedTimestamp=1642531182377, kafka_groupId=anonymous.fa9c0bcf-ba1b-4a0e-be4e-4f997785cf91, timestamp=1642531182464}] 성공이다.\n참조\n Spring Cloud Stream With Kafka ","permalink":"https://www.springboot.kr/posts/spring/spring-cloud-stream-binder-kafka/","summary":"Spring Cloud Stream Binder Kafka를 위한 글.\n버전이 올라오면서 @OUTPUT 어노테이션이 Deprecated 돼서 찾아봤는데 StreamBridge를 이용하면 된다 한다.\n일단 처음부터 봐봅시다.\nimplementation group: \u0026#39;org.springframework.cloud\u0026#39;, name: \u0026#39;spring-cloud-stream-binder-kafka\u0026#39;, version: \u0026#39;3.2.1\u0026#39; 를 추가하면 5개의 라이브러리가 추가 된다.\nGradle: org.springframework.cloud:spring-cloud-function-context:3.2.1 Gradle: org.springframework.cloud:spring-cloud-function-core:3.2.1 Gradle: org.springframework.cloud:spring-cloud-stream:3.2.1 Gradle: org.springframework.cloud:spring-cloud-stream-binder-kafka:3.2.1 Gradle: org.springframework.cloud:spring-cloud-stream-binder-kafka-core:3.2.1 그럼 시작해보자.\n우선 application.yml에 cloud kafka를 설정해줍시다.\ncloud: function: definition: consumer;producer stream: kafka: bindings: producer-out-0: producer: configuration: key: serializer: org.","title":"StreamBridge 사용하기"},{"content":"오늘은 ELK랑 씨름했다. 왜냐면 내가 지금 해외에 있으니까 씨름했다.\n우선 지금 개발할 때 쓰는 건 5년 넘은 맥북 듀얼코어에 8기가인데\n프로젝트를 MSA로 하니까 서비스만 5개를 돌리고 도커 안에 컨테이너도 여러개 띄우고 카프카도 키고 하니까 하루도 빠짐없이 비행기 이륙을 시도한다.\n거기다 elk가 필요하게 됐는데 맨날 이륙하는 맥북에 뭔가를 더하기 싫어서 한국 집에 있는 rpi에 elk 설치를 시도했다.\n결국엔 되긴 했는데 ek만 됐다 l빼고ㅋㅋ\nek는 rpi에 깔고 logstash는 맥에 깔았다.\n그리고 연결하는 데 애를 좀 먹었다. 카프카 때문에. 결국 다 되긴 했는데.\n4시간 정도 잡아먹힌 거 같다. 내일부터는 다시 서버 개발 시작이다.\n- 끗 - ","permalink":"https://www.springboot.kr/posts/blahblah/fight-with-elk/","summary":"오늘은 ELK랑 씨름했다. 왜냐면 내가 지금 해외에 있으니까 씨름했다.\n우선 지금 개발할 때 쓰는 건 5년 넘은 맥북 듀얼코어에 8기가인데\n프로젝트를 MSA로 하니까 서비스만 5개를 돌리고 도커 안에 컨테이너도 여러개 띄우고 카프카도 키고 하니까 하루도 빠짐없이 비행기 이륙을 시도한다.\n거기다 elk가 필요하게 됐는데 맨날 이륙하는 맥북에 뭔가를 더하기 싫어서 한국 집에 있는 rpi에 elk 설치를 시도했다.\n결국엔 되긴 했는데 ek만 됐다 l빼고ㅋㅋ\nek는 rpi에 깔고 logstash는 맥에 깔았다.\n그리고 연결하는 데 애를 좀 먹었다.","title":"ELK랑 씨름한날"},{"content":"@Authenticationprincipal !이 어노테이션은 Package org.springframework.security.core.annotation 에 있는 어노테이션 입니다.\ndocs.spring.io에서 확인해 보면 이 어노테이션은 Authentication.getPrincipal()메서드 인수를 확인하는 데 사용된다고 하는데 Authentication 인터페이스에 대해서는 다음에 다루고 우선 이 메서드만 확인해 보게 되면\ngetPrincipal\n인증 중인 주체의 ID입니다.\n사용자 이름과 비밀번호가 있는 인증 요청의 경우, 이 사용자 이름이 됩니다.\n호출자는 인증 요청의 주체를 채워야 합니다.\nAuthenticationManager 구현체는 응용 프로그램에서 사용할 수 있는 주체로서 더 풍부한 정보를 포함하는 인증을 반환하는 경우가 많습니다.\n많은 인증 공급자가 UserDetails 개체를 주체로 만듭니다.\n라고한다. 그럼 이제 @Authenticationprincipal에 대해 한번 확인해보자.\nSpring Security를 사용할 때 @AuthenticationPrincipal은 사용자가 인증된 경우 principal를 주입하고\n사용자가 인증되지 않은 경우 null을 주입하는데 대부분의 로그인 기능에 UserDetails을 상속받아 커스텀으로 유저 디테일을 만들고 시작하기에 아래 코드에서는 TestUserDetails로 받을 수 있다.\n@RestController public class UserController { @GetMapping(\u0026#34;/username\u0026#34;) public String getUserName(@AuthenticationPrincipal TestUserDetails testUserDetails) { return testUserDetails.getUsername(); } } 만약에 @Authenticationprincipal를 사용하지 않는다면 SecurityContextHolder를 사용해 Principal를 가져올 수 있다.\n@RestController public class UserController { @GetMapping(\u0026#34;/username\u0026#34;) public String getUserName(@AuthenticationPrincipal TestUserDetails testUserDetails) { SecurityContext context = SecurityContextHolder.getContext(); Authentication authentication = context.getAuthentication(); TestUserDetails testUserDetails = (TestUserDetails) authentication.getPrincipal(); return testUserDetails.getUsername(); } } 참조\n Annotation Type AuthenticationPrincipal Interface Authentication [Spring Boot] How to stub @AuthenticationPrincipal argument with Spring-Security in Unit Tests Spring Security: Authentication and Authorization In-Depth - 끗 - ","permalink":"https://www.springboot.kr/posts/spring/authenticationprincipal-security/","summary":"@Authenticationprincipal !이 어노테이션은 Package org.springframework.security.core.annotation 에 있는 어노테이션 입니다.\ndocs.spring.io에서 확인해 보면 이 어노테이션은 Authentication.getPrincipal()메서드 인수를 확인하는 데 사용된다고 하는데 Authentication 인터페이스에 대해서는 다음에 다루고 우선 이 메서드만 확인해 보게 되면\ngetPrincipal\n인증 중인 주체의 ID입니다.\n사용자 이름과 비밀번호가 있는 인증 요청의 경우, 이 사용자 이름이 됩니다.\n호출자는 인증 요청의 주체를 채워야 합니다.\nAuthenticationManager 구현체는 응용 프로그램에서 사용할 수 있는 주체로서 더 풍부한 정보를 포함하는 인증을 반환하는 경우가 많습니다.\n많은 인증 공급자가 UserDetails 개체를 주체로 만듭니다.","title":"@Authenticationprincipal에 대하여"},{"content":"이번 게시물은 JPA를 사용할 때 querydsl 없이 여러 엔티티를 join 하는 방법이다.\n단 조건으로 엔티티 중 다른 엔티티 간의 중개자 역할을 하는 엔티티가 필요하다 매핑이 되어 있어야 한다.\n코드로 보자\n@Entity public class Company { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(mappedBy = \u0026#34;company\u0026#34;, fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) @JoinColumn(name = \u0026#34;company_id\u0026#34;) private List\u0026lt;Product\u0026gt; products = new ArrayList\u0026lt;\u0026gt;(); @OneToOne(mappedBy = \u0026#34;company\u0026#34;, fetch = FetchType.LAZY) private Employee employee; } @Entity public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = \u0026#34;company_id\u0026#34;) private Company company; } @Entity public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; } 이 코드를 보면 Company는 2개의 다른 엔티티와 연결돼있는 상태이다.\n이 상태에서 본다면 Product와 Employee는 서로 관계가 없는 엔티티처럼 보이지만\nCompany라는 중개자 엔티티를 통해 서로 연결이 가능하다.\n사용방법은 이러하다 밑의 레포지토리를 보자.\n@Repository public interface EmployeeRepository extends JpaRepository\u0026lt;Employee, Long\u0026gt; { List\u0026lt;Employee\u0026gt; findByCompany_Products_Name(String productsName); } 위의 코드를 보면 레포지토리는 EmployeeRepository이다.\n메서드를 보면 findBy 후에 Employee에 있는 Company를 불러오고 _를 사용해 Company 안에 있는 Products를 불러온다.\n그 후 _를 사용해 Product 안의 name까지 불러온다. 그렇게 해서 완성된 메서드는 findByCompany_Products_Name이다\n이렇게 메서드가 작성되면 실행되는 쿼리는 Product의 name을 찾는 쿼리를 만든다\n결과는 다음과 같다.\nHibernate: select employee0_.id as id1_1_, employee0_.company_id as company_3_1_, employee0_.name as name2_1_ from employee employee0_ left outer join company company1_ on employee0_.company_id=company1_.id left outer join product products2_ on company1_.id=products2_.company_id where products2_.name=? employee로 시작해서 company를 left outer join 그리고 product를 left outer join 해서 product name을 찾는 걸 볼 수 있다.\n단 사용에 주의할 점은 엔티티 내부에 매핑을 잘해줘야 한다는 것이다.\n만약 저 상태에서 Company에 Product가 없고 Product 엔티티 안에 단방향으로 돼있을 경우에는 안된다.\n- 끗 - ","permalink":"https://www.springboot.kr/posts/spring/jpa-multiple-mapping-domain-join/","summary":"이번 게시물은 JPA를 사용할 때 querydsl 없이 여러 엔티티를 join 하는 방법이다.\n단 조건으로 엔티티 중 다른 엔티티 간의 중개자 역할을 하는 엔티티가 필요하다 매핑이 되어 있어야 한다.\n코드로 보자\n@Entity public class Company { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(mappedBy = \u0026#34;company\u0026#34;, fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) @JoinColumn(name = \u0026#34;company_id\u0026#34;) private List\u0026lt;Product\u0026gt; products = new ArrayList\u0026lt;\u0026gt;(); @OneToOne(mappedBy = \u0026#34;company\u0026#34;, fetch = FetchType.","title":"JPA 다중 매핑 엔티티 Querydsl 없이 Join 하기"},{"content":"JPA N+1은 많이 겪어본 문제일듯한데.\n쉬운 방법으로 해결하는 fetch join 방법을 소개한다.\n우선 기본적으로 많이 쓰이는 3가지를 소개한다.\n @NamedEntityGraph 와 @EntityGraph 를 사용해 해결하기 Querydsl 을 사용해 해결하기 JPQL 을 사용해 해결하기 나는 여기 중에서 1번과 비슷하게 사용하는 방법을 소개한다.\n코드를 보자\n우선 Entity 클래스들부터 보자 Company와 Product가 있다.\n@Entity public class Company { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(mappedBy = \u0026#34;company\u0026#34;, fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) private List\u0026lt;Product\u0026gt; products = new ArrayList\u0026lt;\u0026gt;(); @Builder public Company(Long id, String name, List\u0026lt;Product\u0026gt; products) { this.id = id; this.name = name; this.products = products; } public void setProducts(List\u0026lt;Product\u0026gt; products) { this.products = products; } } @Entity public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = \u0026#34;company_id\u0026#34;) private Company company; @Builder public Product(Long id, String name, Company company) { this.id = id; this.name = name; this.company = company; } public void setCompany(Company company) { this.company = company; } } 다음으로 Repository를 보자\n@Repository public interface CompanyRepository extends JpaRepository\u0026lt;Company, Long\u0026gt; { @Override List\u0026lt;Company\u0026gt; findAll(); } 데이터는 다음과 같다.\nCompany Table\n id Name 7 Coke 8 Nike Product Table\n id Name company_id 9 CocaCola 7 10 Sprite 7 11 ZeroCola 7 12 Shoes 8 우선 이렇게 돼있는 코드를 테스트 돌려보자.\n@SpringBootTest public class JpaNPlusOneTest { @Autowired CompanyRepository companyRepository; @Test void findCompanyTest() { companyRepository.findAll(); } } Hibernate: select company0_.id as id1_0_, company0_.name as name2_0_ from company company0_ Hibernate: select products0_.company_id as company_3_1_0_, products0_.id as id1_1_0_, products0_.id as id1_1_1_, products0_.company_id as company_3_1_1_, products0_.name as name2_1_1_ from product products0_ where products0_.company_id=? Hibernate: select products0_.company_id as company_3_1_0_, products0_.id as id1_1_0_, products0_.id as id1_1_1_, products0_.company_id as company_3_1_1_, products0_.name as name2_1_1_ from product products0_ where products0_.company_id=? 결과를 보게 되면 company를 찾고 찾은 company를 1개씩 product에서 검색한다.\n그럼 이제 여기서 Repository 위에 이번에 글에서 말하고 싶은 @EntityGraph(attributePaths = {\u0026quot;products\u0026quot;}, type = EntityGraph.EntityGraphType.LOAD) 어노테이션을 붙이고 실행해 보자\n@Repository public interface CompanyRepository extends JpaRepository\u0026lt;Company, Long\u0026gt; { @EntityGraph(attributePaths = {\u0026#34;products\u0026#34;}, type = EntityGraph.EntityGraphType.LOAD) @Override List\u0026lt;Company\u0026gt; findAll(); } Hibernate: select company0_.id as id1_0_0_, products1_.id as id1_1_1_, company0_.name as name2_0_0_, products1_.company_id as company_3_1_1_, products1_.name as name2_1_1_, products1_.company_id as company_3_1_0__, products1_.id as id1_1_0__ from company company0_ left outer join product products1_ on company0_.id=products1_.company_id 결과를 보면 3번의 조회에서 1번의 조회로 해결된 것을 볼 수가 있다.\n글 위에서 소개한 1번의 내용 중 @NamedEntityGraph를 사용하지 않고 @EntityGraph만으로도 사용이 가능하다.\n더 간결해 편해질 수 있다.\n참고로 @EntityGraph에 있는 type으로는 2가지가 있는데\nEnum Constant Detail LOAD EntityGraph.EntityGraphType LOAD\njavax.persistence.loadgraph 속성을 사용하여 엔티티 그래프를 지정할 때 엔티티 그래프의 속성 노드에 의해 지정된 속성은 FetchType.EAGER로 처리되고 지정되지 않은 속성은 지정된 또는 기본 FetchType에 따라 처리됩니다.\nFETCH EntityGraph.EntityGraphType FETCH\njavax.persistence.fetchgraph 속성을 사용하여 엔티티 그래프를 지정할 때 엔티티 그래프의 속성 노드에 의해 지정된 속성은 FetchType.EAGER로 처리되고 지정되지 않은 속성은 FetchType.LAZY로 처리됩니다.\n라고 Enum EntityGraph.EntityGraphType 여기에 되있다\n이글 [Spring Data] @EntityGraph로 lazy 패치 같이 불러오기 (fetch-graph 커스터마이징) 도 참조 했다.\n- 끗 - ","permalink":"https://www.springboot.kr/posts/spring/jpa-n-plus-1-problem-with-entity-graph/","summary":"JPA N+1은 많이 겪어본 문제일듯한데.\n쉬운 방법으로 해결하는 fetch join 방법을 소개한다.\n우선 기본적으로 많이 쓰이는 3가지를 소개한다.\n @NamedEntityGraph 와 @EntityGraph 를 사용해 해결하기 Querydsl 을 사용해 해결하기 JPQL 을 사용해 해결하기 나는 여기 중에서 1번과 비슷하게 사용하는 방법을 소개한다.\n코드를 보자\n우선 Entity 클래스들부터 보자 Company와 Product가 있다.\n@Entity public class Company { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(mappedBy = \u0026#34;company\u0026#34;, fetch = FetchType.","title":"JPA N+1문제와 @EntityGraph"},{"content":"나는 최근 카툰을 볼 수 있는 서비스를 만들고 있다.\n이름은 TheCarbToon.\n스프링 클라우드를 사용해 서비스들을 묶어 개발을 진행 중이다.\n|-------------------------------------------| | | | Auth(java)---\\ | | | \\ | | | \\ | | User(java)-------------- Gateway(java) | | | / | | | | / | | | Media(kotlin)-/ | | | | | | | | | | | Discovery(java) ------------| | | | |-------------------------------------------| 현재 진행은 이러하고 서비스가 추가될 수도 있다고 생각한다.\n오늘이 이제 3일차가 되는 날인데 갑자기 일지를 작성하는 이유는 원래 프로젝트를 시작할 때 일지도 작성하면서 해야지 했는데 막상 시작하니 바쁘기도 하고 개발하느라 바빠서 적지 않았는데 오늘 갑자기 카프카 데이터를 처리하던 도중에 문뜩 생각이 들어 작성하게 됐다.\n일단 디스커버리와 게이트웨이까지는 얼추 된 것 같다.\n1,2일 차에 인증서버에서 발급된 토큰으로 필터에서 거르는 작업을 하느라 시간이 금방 간 거 같다.\n지금 진행 중인 부분은 인증과 유저 부분이고 인증 부분과 유저 부분을 분리해 따로 관리하려 한다.\n하지만 유저를 저장하는 DB는 인증과 유저에 따로 있고 인증은 몽고로 들어있고 유저는 mysql로 들어가 있다.\n이유는 인증 부분은 인증만 따로 처리하고 유저 관리는 유저 서비스에서만 하고 싶어서이다.\n인증 서비스에서 유저를 생성을 받고 생성이 되면 유저 서비스에서 똑같은 유저를 생성한다.\n그리고 토큰 발급을 제외한 나머지 유저와 관련된 API들은 전부 유저 서비스에서 관리하게 한다.\n물론 유저 서비스에서 변경된 사항들은 인증서버에도 적용되게 하려 한다.\n그래서 지금 인증 부분에서는 회원가입, 토큰 발급이 됐고 모든 서비스들은 인증에서 발급된 토큰으로 필터로 거른다.\n유저 서비스는 계속 다듬고 있는 중이고 계속 테스트를 진행 중이다.\n오늘은 여기까지만 해야겠다.\n나중에 개발이 다 끝나면 다 정리해서 올릴 생각이다.\n모든 코드는 여기에서 볼수있따\n- 끗 - ","permalink":"https://www.springboot.kr/posts/devnote/thecarbtoon-1/","summary":"나는 최근 카툰을 볼 수 있는 서비스를 만들고 있다.\n이름은 TheCarbToon.\n스프링 클라우드를 사용해 서비스들을 묶어 개발을 진행 중이다.\n|-------------------------------------------| | | | Auth(java)---\\ | | | \\ | | | \\ | | User(java)-------------- Gateway(java) | | | / | | | | / | | | Media(kotlin)-/ | | | | | | | | | | | Discovery(java) ------------| | | | |-------------------------------------------| 현재 진행은 이러하고 서비스가 추가될 수도 있다고 생각한다.","title":"TheCarbtoon 개발일지 - 1"},{"content":"영화 더디그를 봤다.\n보는데 이런 대사가 나왔다.\nPretty(여주) - The servants tell me you’ve studied everything, from Latin to geology. Brown(남주) - Well, a little education is a dangerous thing. (사실 education 보다는 knowledge도 어울린다고 생각하는데 education 음.. 모르겠다)\n(바실 브라운 (Basil Brown) 고고학자)\n이 대사를 보면서 영화 내에서도 나이가 있는 사람이지만 끊임없이 공부를 한다는 점 그런 게 와 닺는 거 같다.\n남자 주인공이 공부에 대해 집착(?) 아닌 집착을 하는 이유는 지식과 현장 경험은 많지만 학력이 낮아\n사회적으로 무시당하는 그런 것 때문이 아닐까?\n실제로 브라운은 유물 발굴에서 처음 공로자로 인정을 받지 못했는데 지금은 인정이 된 상태다.\n브라운 처럼 멋진사람이 되자.\n","permalink":"https://www.springboot.kr/posts/blahblah/the-dig/","summary":"영화 더디그를 봤다.\n보는데 이런 대사가 나왔다.\nPretty(여주) - The servants tell me you’ve studied everything, from Latin to geology. Brown(남주) - Well, a little education is a dangerous thing. (사실 education 보다는 knowledge도 어울린다고 생각하는데 education 음.. 모르겠다)\n(바실 브라운 (Basil Brown) 고고학자)\n이 대사를 보면서 영화 내에서도 나이가 있는 사람이지만 끊임없이 공부를 한다는 점 그런 게 와 닺는 거 같다.\n남자 주인공이 공부에 대해 집착(?) 아닌 집착을 하는 이유는 지식과 현장 경험은 많지만 학력이 낮아","title":"어설픈 지식은 위험하다."},{"content":"Field testService in com.example.TestController required a single bean, but 3 were found: - testServiceImpl1: defined in file [build/classes/java/main/com/example/TestServiceImpl1.class] - testServiceImpl2: defined in file [build/classes/java/main/com/example/TestServiceImpl2.class] - testServiceImpl3: defined in file [build/classes/java/main/com/example/TestServiceImpl3.class] Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed @Primary, @Qualifier 이런 문제에서 생기는 스프링 에러다.\n빈이 여러 개인 경우 생기는 문제인데\n밑이 보면 해결 방법으로 3가지를 제시해 준다.\n Consider marking one of the beans as @Primary updating the consumer to accept multiple beans using @Qualifier to identify the bean that should be consumed 예제 코드로 확인해보자\nTestController\n@RequiredArgsConstructor @RestController public class TestController { private final TestService testService; @GetMapping(\u0026#34;\u0026#34;) public String test() { return String.valueOf(testService.test()); } } TestService\npublic interface TestService { int test(); } @Service public class TestServiceImpl1 implements TestService { @Override public int test() { return 1; } } @Service public class TestServiceImpl2 implements TestService { @Override public int test() { return 2; } } @Service public class TetServiceImpl3 implements TestService { @Override public int test() { return 3; } } 이렇게 돼있을 때 실행 시 게시글 상단의 에러를 만날 수 있다.\n@Primary 그럼 첫 번째 방법을 사용해 본다.\n@Primary @Service public class TestServiceImpl1 implements TestService { @Override public int test() { return 1; } } 내가 원하는 클래스에 @Primary를 붙여주고 확인하면 그 클래스로 실행이 된다.\n나는 TestServiceImpl1에 붙여줬으니 1이 나온다.\nList 다음으로 두 번째 방법은 List로 만들어 사용하는 것이다.\n@RequiredArgsConstructor @RestController public class TestController { private final List\u0026lt;TestService\u0026gt; testServiceList; @GetMapping(\u0026#34;\u0026#34;) public String test() { return String.valueOf(testServiceList.get(1).test()); } } 이렇게 하게 되면 3개의 클래스 모두 사용 가능한데 여기서 get()으로 인덱스 값을 넣어서 가져오면 된다.\n리스트에 들어가는 순서는 클래스 이름순이다.\n따로 명시하지 않아도 TetServiceImpl1 -\u0026gt; tetServiceImpl1, TetServiceImpl2 -\u0026gt; tetServiceImpl2 이렇게 된다.\n@Qualifier 마지막으로 세 번째 방법으로는 @Qualifier를 사용하는 것이다.\n세 번째 방법에는 여러 가지의 방법이 있다.\n@RestController public class TestController { @Autowired @Qualifier(\u0026#34;testServiceImpl3\u0026#34;) private TestService testService; @GetMapping(\u0026#34;\u0026#34;) public String test() { return String.valueOf(testService.test()); } } 위의 코드처럼 @Autowired를 사용할 경우 @Qualifier(\u0026quot;\u0026quot;) 명시해 주면 된다.\n생성자를 통한 주입의 경우에는 밑에처럼 사용하면 된다.\n@RestController public class TestController { private final TestService testService; public TestController(@Qualifier(\u0026#34;testServiceImpl3\u0026#34;) TestService testService) { this.testService = testService; } @GetMapping(\u0026#34;\u0026#34;) public String test() { return String.valueOf(testService.test()); } } 마지막으로 Lombok의 @RequiredArgsConstructor와 같이 사용할 경우에는 문제가 있지만 이 방법으로 사용하기 위해서는 프로젝트의 루트 폴더에 밑에 lombok.config 파일을 만들어주고 아래의 내용을 적어주면 된다.\nlombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier 작성이 끝났다면 RequiredArgsConstructor와 함께 사용해 보자\n@RequiredArgsConstructor @RestController public class TestController { @Qualifier(\u0026#34;testServiceImpl3\u0026#34;) private final TestService testService; @GetMapping(\u0026#34;\u0026#34;) public String test() { return String.valueOf(testService.test()); } } 이렇게 해주면 고민이 해결된다.\n마지막으로 기본으로 설정되는 이름 말고 본인이 원하는 이름을 사용하고 싶은 경우에는 아래와 같이 사용하자\n@Qualifier(\u0026#34;test1\u0026#34;) @Service public class TestServiceImpl1 implements TestService { @Override public int test() { return 1; } } @Qualifier(\u0026#34;test2\u0026#34;) @Service public class TestServiceImpl2 implements TestService { @Override public int test() { return 2; } } 마지막 @RequiredArgsConstructor \u0026amp; @Qualifier의 참조 링크\nSPRING + LOMBOK + @QUALIFIER OR INJECTION JUST BECAME A BIT EASIER (PART 2 OF 2)\n- 끗 - ","permalink":"https://www.springboot.kr/posts/spring/required-a-single-bean-but-3-were-found/","summary":"Field testService in com.example.TestController required a single bean, but 3 were found: - testServiceImpl1: defined in file [build/classes/java/main/com/example/TestServiceImpl1.class] - testServiceImpl2: defined in file [build/classes/java/main/com/example/TestServiceImpl2.class] - testServiceImpl3: defined in file [build/classes/java/main/com/example/TestServiceImpl3.class] Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed @Primary, @Qualifier 이런 문제에서 생기는 스프링 에러다.\n빈이 여러 개인 경우 생기는 문제인데\n밑이 보면 해결 방법으로 3가지를 제시해 준다.","title":"required a single bean, but 3 were found 해결법"},{"content":"블로그 새롭게 만들기 성공\n","permalink":"https://www.springboot.kr/posts/blahblah/renew-blog/","summary":"블로그 새롭게 만들기 성공","title":"블로그 새롭게 만들기 성공"},{"content":"겨울이 끝나면 봄이 온다. 봄이 온다. 봄이 올까? 왜 봄일까? 봄은 짧은데 여름아니면 겨울하면안되나? 여름은 안되나? 겨울은?\n","permalink":"https://www.springboot.kr/posts/blahblah/2021-12-27-blahblah/","summary":"겨울이 끝나면 봄이 온다. 봄이 온다. 봄이 올까? 왜 봄일까? 봄은 짧은데 여름아니면 겨울하면안되나? 여름은 안되나? 겨울은?","title":"블라블라"},{"content":"Chapter 5. 책임 할당하기 데이터 중심 설계로 인해 발생하는 문제점을 해결할 수 있는 가장 기본적인 방법은 데이터가 아닌 책임에 초점을 맞추는 것이다.\n책임에 초점을 맞춰서 설계할 때 직면하는 가장 큰 어려움은 어떤 객체에게 어떤 책임을 할당할지를 결정하기가 쉽지 않다는 것이다.\n책임 할당 과정은 일종의 트레이드오프 활동이다. 동일한 문제를 해결할 수 있는 다양한 책임 할당 방법이 존재하며 어떤 방법이 최선인지는 상황과 문맥에 따라 달라진다.\n따라서 올바른 책임을 할당하기 위해서는 다양한 관점에서 설계를 평가할 수 있어야 한다.\n이번 장에서는 GRASP 패턴을 배우고 응집도와 결합도 캡슐화 같은 다양한 기준에 따라 책임을 할당하고 결과를 트레이드오프 할 수 있는 기준을 배울 것이다.\nGRASP Pattern이란?\n데이터 중심의 설계에서 책임 중심의 설계로 전환하기 위해서는 다음의 두 가지 원칙을 따라야 한다.\n 데이터보다 행동을 먼저 결정하라 협력이라는 문맥 안에서 책임을 결정하라 데이터보다 행동을 먼저 결정하라 ","permalink":"https://www.springboot.kr/posts/object/chapter5/","summary":"Chapter 5. 책임 할당하기 데이터 중심 설계로 인해 발생하는 문제점을 해결할 수 있는 가장 기본적인 방법은 데이터가 아닌 책임에 초점을 맞추는 것이다.\n책임에 초점을 맞춰서 설계할 때 직면하는 가장 큰 어려움은 어떤 객체에게 어떤 책임을 할당할지를 결정하기가 쉽지 않다는 것이다.\n책임 할당 과정은 일종의 트레이드오프 활동이다. 동일한 문제를 해결할 수 있는 다양한 책임 할당 방법이 존재하며 어떤 방법이 최선인지는 상황과 문맥에 따라 달라진다.\n따라서 올바른 책임을 할당하기 위해서는 다양한 관점에서 설계를 평가할 수 있어야 한다.","title":"[오브젝트 OBJECTS] Chapter 5. 책임 할당하기"},{"content":"Chapter 4. 설계 품질과 트레이드오프 객체지향 설계의 핵심은 역할,책임,협력이다.\n협력은 애플리케이션의 기능을 구현하기 위해 메시지를 주고받는 객체들 사이의 상호작용 이다. 책임은 객체가 다른 객체와 협력 하기 위해 수행하는 행동이고 역할은 대체 가능한 책임의 집합이다.\n책임 주도 설계라는 이름에 나오는 것처럼 저 셋중 가장 중요한 것은 책임이다.\n책임이 객체지향 애플리케이션 전체의 품질을 결정한다.\n객체지향 설계란 올바른 객체에게 올바른 책임을 할당하면서 낮은 결합도와 높은 응집도를 가진 구조를 창조하는 활동이다.\n객체지향 설계에 관한 두 가지 관점이 섞여 있다.\n 객체지향 설계의 핵심이 책임이라는 것. 책임을 할당하는 작업이 응집도와 결합도 같은 설계 품질과 깊이 연관돼 있다는 것. 훌륭한 설계는 합리적인 비용안에서 변경을 수용할 수 있는 구조를 만드는 것이다.\n객체를 단순한 데이터의 집합으로 바라보는 시각은 객체의 내부 구현을 퍼블릭 인터페이스에 노출시키는 결과를 낳기 때문에 설계가 변경에 취약해 진다.\n이런 문제를 피할 수 있는 가장 좋은 방법이 객체의 책임에 초점을 맞추는 것이다.\n객체지향 설계에서는 두 가지 방법을 이용해 시스템을 객체로 분할할 수 있다.\n 상태를 분할의 중심축으로 삼는 방법. 책임을 분할의 중심축으로 삼는 방법. 훌륭한 객체지향 설계는 데이터가 아니라 책임에 초점을 맞춰야 한다.\n객체의 상태는 구현에 속한다. 구현은 불안정하기 때문에 변하기 쉽다.\n상태를 객체 분할의 중심축으로 삼으면 구현에 관한 세부사항이 객체의 인터페이스에 의존하는 모든 객체에게 변경의 영향이 퍼진다.\n따라서 데이터에 초첨을 맞춘 설계는 변경에 취약할 수밖에 없다.\n객체의 책임은 인터페이스에 속한다. 객체는 책임을 드러내는 안정적인 인터페이스 뒤로 책임을 수행하는데 필요한 상태를 캡슐화해 구현 변경에 대한 파장이 외부로 퍼져나가는 것을 방지한다.\n책임에 초점을 맞추면 상대적으로 변경에 안정적인 설계를 얻을 수 있다.\n캡슐화 상태와 행동을 하나의 객체에 모으는 이유는 객체의 내부 구현을 외부로부터 감추기 위해서다.\n객체지향이 강력한 이유는 한 곳에서 일어난 변경이 전체 시스템에 영향을 끼치지 않도록 파급효과를 적절하게 조절할 수 있는 장치를 제공하기 때문이다.\n변경이될 가능성이 높은 부분을 구현이라 하고 상대적으로 안정적인 부분을 인터페이스라 한다.\n객체를 설계하기 위해 가장 기본적인 아이디어는 변경의 정도에 따라 구현과 인터페이스를 분리하고 외부에서는 인터페이스에만 의존하도록 관계를 조절하는 것이다.\n복잡성을 다루기 위한 가장 효과적인 도구는 추상화다. 그리고 주요한 추상화 방법은 캡슐화다.\n하지만 복잡성이 잘 캡슐화될 것이라고 보장할 수는 없다. 객체지향 프로그래밍을 통해 전반적으로 얻을 수 있는 장점은 오직 설계 과정 동안 캡슐화를 목표로 인식할 때 달성될 수 있다.\n설계가 필요한 이유는 요구사항이 변경되기 때문이고 캡슐화가 중요한 이유는 불안정한 부분과 안전적인 부분을 분리해 변경의 영향을 통제할 수 있기 떄문이다.\n캡슐화란 어떤 것을 숨긴다는 것을 의미한다. 뜻밖의 피해가 발생할 수 있는 가능성을 사전에 방지해 변경으로 부터 자유로워지는 것이다.\n캡슐화는 우리를 좋은 코드로 안내하기 떄문에 가장 중요한 제1원리다.\n응집도와 결합도 응집도는 모듈에 포함된 내부 요소들이 연관돼 있는 정도를 나타낸다. 모듈 내의 요소들이 하나의 목절을 위해 긴밀하게 협력한다면 높은 응집도를 가지고 서로 다른 목적을 추구하면 낮은 응집도를 가진다.\n객체지향의 관점에서 응집도는 객체 또는 클래스에 얼마나 관련 높은 책임들을 할당했는지 나타낸다.\n결합도는 의존성의 정도를 나타내며 다른 모듈에 대해 얼마나 많은 지식을 갖고 있는지를 나타내는 척도다. 어떤 모듈이 다른 모듈에 대해 너무 자세한 부분까지 알고 있다면 두 모듈은 높은 결합도를 가지고 다른 모듈에 대해 꼭 필요한 지식만 알고 있다면 낮은 결합도를 가진다.\n결합도는 객체 또는 클래스가 협력에 필요한 적절한 수준의 관계만을 유지하고 있는지 나타낸다.\n좋은 설계란 높은 응집도와 낮은 결합도를 가진 모듈로 구성된 설계를 의미하며 오늘의 기능을 수행하면서 내일의 변경을 수용할 수 있는 설계이다.\n높은 응집도와 낮은 결합도를 가진 설계를 추구해야 하는 이유는 설계를 변경하기 쉽게 만들기 때문이다.\n변경의 관점에서 응집도란 변경이 발생할 때 모듈 내부에서 발생하는 변경의 정도로 측정할 수 있다.\n높은 결합도(High coupling)와 낮은 결합도(Low coupling)\n응집도가 높을수록 변경의 대상과 범위가 명확해지기 때문에 코드를 변경하기 쉬워진다.\n결합도는 한 모듈이 변경되기 위해서 다른 모듈의 변경을 요구하는 정도로 측정할 수 있다.\n결합도가 높으면 높을수록 함께 변경해야 하는 모듈의 수가 늘어나기 때문에 변경하기 어려워진다.\n낮은 결합도를 가진 설계는 모듈을 변경했을 때 오직 하나의 모듈만 영향을 받지만 높은 결합도를 가진 설계는 모듈을 변경했을 떄 다른 모듈을 동시에 변경해야 한다.\n클래스의 구현이 아닌 인터페이스에 의존하도록 코드를 작성해야 낮은 결합도를 얻을 수 있다.\n인터페이스에 대해 프로그래밍하라\nWhat Does It Mean to Program to Interfaces?\n결합도가 높아도 상관 없는 경우도 있지만 직접 장성한 코드의 경우에는 항상 불안정하며 언제라도 변경될 가능성이 높기 때문에 코드를 완성한 그 순간부터 코드를 수정할 준비를 해야한다.\n그렇기 때문에 직접 작성한 코드의 경우에는 낮은 결합도를 유지하려 노력해야 한다.\n응집도와 결합도는 변경과 관련이 깊기 때문에 변경의 관점에서 바라보는 것은 설계에 대해 시각을 크게 변화시킬 수 있다.\n캡슐화의 정도는 응집도와 결합도에 영향을 미친다.\n캡슐화를 지키면 모듈안의 응집도는 낮아지고 모듈 사이의 결합도는 낮아진다.\n하지만 캡슐화를 위반하면 모듈 안의 응집도는 낮아지고 모듈 사이의 결합도는 높아진다. 그렇기 때문에 응집도와 결합도를 고려하기 전에 먼저 캡슐화를 항상시키기 위해 노력해야한다.\n데이터 중심의 시스템의 문제점 데이터 중심의 설계가 가진 대표적인 문제점은\n 캡슐화 위반 높은 결합도 낮은 응집도 이렇게 3가지로 요약할 수 있다.\n캡슐화 위반 객체에게 중요한 것은 책임이다. 그리고 구현을 캡슐화할 수 있는 적절한 책임은 협력이라는 문맥을 고려할 때만 얻을 수 있다.\n협력에 관해 고민하지 않고 설계를 하면 캡슐화를 위반하는 과도한 접근자와 수정자를 가지게 되는 경향이 있다.\n객체가 사용될 문맥을 추측할 수밖에 없는 경우 개발자는 어떤 상황에서도 해당 객체가 사용될 수 있게 최대한 많은 접근자 메서드를 추가하게 되는 것이다.\n이렇게 접근자와 수정자에 과도하게 의존하는 설계 방식을 추측에 의한 설계 전략(design by guessing strategy)라고 한다.\n막연한 추측을 기반으로 설계를 하게 되면 프로그래머는 내부 상태를 드러내는 메서드를 최대한 많이 추가해야 한다는 압박에 시달리게 되고 결과적으로 대부분의 내부 구현이 퍼블릭 인터페이스에 그대로 노출된다.\n그 결과 캡슐화의 원칙을 위반하는 변경에 취약한 설계를 하게 된다.\n높은 결합도 객체 내부의 구현이 객체의 인터페이스에 드러난다는 것은 클라이언트가 구현에 강하게 결합된다는 것을 의미한다.\n결합도 측면에서 데이터 중심 설계가 가지는 또 다른 단점은 여러 데이터 객체들을 사용하는 제어 로직이 특정 객체 안에 집중되기 때문에 하나의 제어 객체가 다수의 데이터 객체에 강하게 결합된다는 것이다.\n이 결합도로 인해 어떤 데이터 객체를 변경하더라도 제어 객체를 함께 변경할 수밖에 없다.\n데이터 중심의 설계는 전체 시스템을 하나의 거대한 의존성 덩어리로 만들어 버리기 때문에 어떤 변경이라도 일단 발생하고 나면 시스템 전체가 요동칠 수밖에 없다.\n낮은 응집도 서로 다른 이유로 변경되는 코드가 하나의 모듈 안에 공존할 때 모듈의 응집도가 낮다고 말한다.\n낮은 응집도는 두 가지 측면에서 설계에 문제를 일으킨다.\n 변경의 이유가 서로 다른 코드들을 하나의 모듈 안에 뭉쳐놓았기 떄문에 변경과 아무 상관이 없는 코드들이 영향을 받게 된다. 어떤 코드를 수정한 후에 아무런 상관도 없던 코드에 문제가 발생하는 것은 모듈의 응집도가 낮을 때 발생하는 대표적인 증상이다.\n 하나의 요구사항 변경을 반영하기 위해 동시에 여러 모듈을 수정해야 한다. 응집도가 낮을 경우 다른 모듈에 위치해야 할 책임의 일부가 엉뚱한 곳에 위치하게 되기 때문이다.\n 어떤 요구사항 변경을 수용하기 위해 하나 이상의 클래스를 수정해야 하는 것은 설계의 응집도가 낮다는 증거이다.\n단일 책임 원칙\n단일 책임 원칙-책임 고립시키기(최소한의 책임을 가져라)\n캡슐화를 지켜라 캡슐화는 설계의 제1원리다.\n데이터 중심의 설계가 낮은 응집도와 높은 결합도로 문제가 생기게 된 근본적인 원인은 바로 캡슐화의 원칙을 위반했기 때문이다.\n객체는 자신이 어떤 데이터를 가지고 있는지를 내부에 캡슐화하고 외부에 공개해서는 안된다.\n스스로의 상태를 책임져야 하며 외부에서는 인터페이스에 정의된 메서드를 통해서만 상태에 접근할 수 있어야 한다.\n스스로 자신의 데이터를 책임지는 객체 상태와 행동을 객체라는 하나의 단위로 묶는 이유는 객체 스스로 자신의 상태를 처리할 수 있게 하기 위해서다.\n객체는 단순한 데이터 제공자가 아니다. 객체 내부에 저장되는 데이터보다 객체가 협력에 참여하면서 수행할 책임을 정의하는 오퍼레이션이 더 중요하다.\n객체를 설계할때 두개의 개별적인 질문으로 분리해야 한다.\n 이 객체가 어떤 데이터를 포함해야 하는가? 이 객체가 데이터에 대해 수행해야 하는 오퍼레이션은 무엇인가? 두 질문은 조합하면 객체 내부 상태를 저장하는 방식과 저장된 상태에 대해 호출할 수 있는 오퍼레이션의 집합을 얻을 수 있다.\n즉 새로운 데이터 타입을 만들 수 있다.\n캡슐화의 진정한 의미 캡슐화는 변경될 수 있는 어떤 것이라도 감추는 것을 의미한다.\n내부 속성을 외부로부터 감추는 것은 데이터 캡슐화라고 불리는 캡슐화의 한 종류일 뿐이다.\n캡슐화라는 것은 변할 수 있는 어떤 것이라도 감추는 것이다.\n그것이 속성의 타입이건 정책의 종류건 상관 없이 내부 구현의 변경으로 인해 외부의 객체가 영향을 받는다면 캡슐화를 위반한 것이다.\n설계에서 변하는 것이 무엇인지 고려하고 변하는 개념을 캡슐화해야 한다.\n높은 결합도 캡슐화 위반으로 내부 구현이 외부로 노출된 경우 클래스 사이의 결합 도는 높을 수밖에 없다.\n두 객체 사이에 결합도가 높을 경우 한 객체의 구현을 변경할 때 다른 객체에게 변경의 영향이 전파될 확률이 높아진다는 사실을 기억하자.\n유연한 설계를 창조하기 위해서는 캡슐화를 설계의 첫 번째 목표로 삼아야 한다.\n데이터 중심 설계는 객체의 행동보다는 상태에 초점을 맞춘다 데이터 주도 설계는 설계를 시작하는 처음부터 데이터에 관해 결정하도록 강요하기 때문에 너무 이른 시기에 내부 구현에 초점을 맞추게 된다.\n데이터 중심 설계 방식에 익숙한 개발자들은 일반적으로 데이터와 기능을 분리하는 절차적 프로그래밍 방식을 따른다.\n이것은 상태와 행동을 하나의 단위로 캡슐화하는 객체지향 패러다임에 반하는 것이다.\n데이터 중심의 관점에서는 객체는 그저 단순한 데이터의 집합체일 뿐이다. 이로 인해 접근자와 수정자를 과도하게 추가하게 되고 이 데이터 객체를 사용하는 절차를 분리된 별도의 객체 안에 구현하게 된다.\n데이터를 처리하는 작업과 데이터를 같은 객체 안에 두더라도 데이터에 초점이 맞춰져 있다면 만족스러운 캡슐화를 얻기 어렵다.\n데이터를 먼저 결정하고 데이터를 처리하는 데 필요한 오퍼레이션을 나중에 결정하는 방식은 데이터에 관한 지식이 객체의 인터페이스에 고스란히 드러나게 되며 결과적으로 객체의 인터페이스는 구현을 캡슐화하는 데 실패하고 코드는 변경에 취약해진다.\n데이터 중심의 설계는 너무 이른 시기에 데이터에 대해 고민하기 때문에 캡슐화에 실패하게 되고 객체 내부 구현이 객체의 인터페이스를 어지럽히고 객체의 응집도와 결합도에 나쁜 영향을 미치기 떄문에 변경에 취약한 코드를 낳게 된다.\n데이터 중심 설계는 객체를 고립시킨 채 오퍼레이션을 정의하도록 만든다 객체지향 애플리케이션을 구현한다는 것은 협력하는 객체들의 공동체를 구축한다는 것을 의미한다.\n협력이라는 문맥 안에서 필요한 책임을 결정하고 이를 수행할 적절한 객체를 결정하는 것이 가장 중요하다.\n올바른 객체지향 설계의 무게 중심은 항상 객체의 내부가 아니라 외부에 맞춰져 있어야 하고 객체가 내부에 어떤 상태를 가지고 그 상태를 어떻게 관리하는가는 부가적인 문제다.\n중요한 것은 객체가 다른 객체와 협력하는 방법이다.\n하지만 데이터 중심 설계에서는 객체의 외부가 아니라 내부로 향하게 되고 데이터의 세부 정보를 먼저 결정하게 된다.\n이렇게 되면 이미 결정된 상태에ㅅ 다른 객체와의 협력 방법을 고민하기 때문에 이미 구현된 객체의 인터페이스를 억지로 끼워 맞출 수밖에 없다.\n","permalink":"https://www.springboot.kr/posts/object/chapter4/","summary":"Chapter 4. 설계 품질과 트레이드오프 객체지향 설계의 핵심은 역할,책임,협력이다.\n협력은 애플리케이션의 기능을 구현하기 위해 메시지를 주고받는 객체들 사이의 상호작용 이다. 책임은 객체가 다른 객체와 협력 하기 위해 수행하는 행동이고 역할은 대체 가능한 책임의 집합이다.\n책임 주도 설계라는 이름에 나오는 것처럼 저 셋중 가장 중요한 것은 책임이다.\n책임이 객체지향 애플리케이션 전체의 품질을 결정한다.\n객체지향 설계란 올바른 객체에게 올바른 책임을 할당하면서 낮은 결합도와 높은 응집도를 가진 구조를 창조하는 활동이다.\n객체지향 설계에 관한 두 가지 관점이 섞여 있다.","title":"[오브젝트 OBJECTS] Chapter 4. 설계 품질과 트레이드오프"},{"content":"Chapter 3. 역할, 책임, 협력 객체지향 패러다임의 관점에서 핵심은 역할, 책임, 협력이다.\n애플리케이션의 기능을 구현하기 위해 수행하는 상호작용을 협력, 객체 자체가 협력에 참여하기 위해 수행하는 로직을 책임이라 한다.\n객체들이 협력 안에서 수행하는 책임들이 모여 객체가 수행하는 역할을 구성한다\n객체지향의 본질은 협력하는 객체들의 공동체를 창조하는 것이다.\n클래스와 상속은 객체들의 책임과 협력이 어느 정도 자리를 잡은 후에 사용할 수 있는 구현 메커니즘일 뿐이다.\n역할, 책임 협력이 제자리를 찾지 못한 상태라면 응집도 높은 클래스와 중복 없는 상속 계층을 구현한다고 하더라도 애플리케이션이 침몰하는 것을 구원하지는 못한다.\n협력 객체지향 시스템은 자율적인 객체들의 공동체다.\n두 객체 사이의 협력은 하나의 객체가 다른 객체에게 도움을 요청할 때 시작된다.\n객체는 다른 객체의 상세한 내부에 접근할 수 없기 때문에 오직 메시지 전송을 통해서만 자신의 요청을 전달할 수 있다.\n객체 사이의 협력을 설계할 때는 객체를 서로 분리된 인스턴스가 아닌 협력하는 파트너로 인식해야 한다.\n메시지를 수신한 객체는 메서드를 실행해 요청에 응답하고 여기서 객체가 메시지를 처리할 방법을 스스로 선택한다는 점이 중요하다.\n이것은 객체가 자신의 일을 스스로 처리할 수 있는 자율적인 존재라는 것을 의미한다.\n객체를 자율적으로 만드는 가장 기본적인 방법은 내부 구현을 캡슐화하는 것이다.\n캡슐화를 통해 변경에 대한 파급효과를 제한할 수 있기 때문에 자율적인 객체는 변하기도 쉬워진다.\n협력이 설계를 위한 문맥을 결정한다 객체란 상태와 행동을 함께 캡슐화하는 실행 단위다.\n애플리케이션 안에 어떤 객체가 필요하다면 그 이유는 단 하나다.\n그 객체가 어떤 협력에 참여하고 있고 협력에 필요한 적절한 행등을 보유하고 있기 때문이다.\n결론적으로 객체의 행동을 결정하는 것은 객체가 참여하고 있는 협력이다.\n협력이 바뀌면 객체가 제공해야 하는 행동 역시 바뀌어야 한다.\n협력은 객체가 필요한 이유와 객체가 수행하는 행동의 동기를 제공한다.\n책임이란 무엇인가 객체가 협력에 참여하기 위해 수행하는 행동을 책임이라 한다.\n책임이란 객체에 의해 정의되는 응집도 있는 행위의 집합으로 객체가 유지해야 하는 정보와 수행할 수 있는 행동에 대해 개략적으로 서술한 문장이다.\n객체의 책임은 객차가 \u0026lsquo;무엇을 알고 있는가\u0026rsquo;와 \u0026lsquo;무엇을 할 수 있는가\u0026rsquo;로 구성된다\n 하는 것(doing) 객체를 생성하거나 계산을 수행하는 등의 스스로 하는 것 다른 객체의 행동을 시작시키는 것 다른 객체의 활동을 제어하고 조절하는 것 아는 것(knowing) 사적인 정보에 관해 아는 것 관련된 객체에 관해 아는 것 자신이 유도하거나 계사할 수 있는 것에 관해 아는 것 일반적으로 책임과 메시지의 크기는 다르다.\n책임은 메시지보다 추상적이고 개념적으로도 더 크다.\n여러 개의 메시지로 분할되기도 하고 하나의 객체가 수행할 수 있다고 생각했던 책임이 나중에는 여러 객체들이 협력해야만 하는 커다란 책임으로 자라는 것이 일반적이다.\n어떤 책임을 수행하기 위해서는 그 책임을 수행하는데 필요한 정보도 함께 알아야 할 책임이 있는 것이다.\n책임은 객체지향 설계의 핵심 이다.\n객체에게 얼마나 적절한 책임을 할당하느냐가 설계의 전체적인 품질을 결정한다.\n책임 할당 INFORMATION EXPERT(정보 전문가) 패턴\n책임 할당을 위한 GRASP 패턴\n일상 생활에서 도움을 전문가에게 도움을 요청하는 것처럼 객체의 세계에서도 협력에 필요한 지식과 방법을 가장 잘 알고 있는 객체에게 도움을 요청한다.\n이 요청에 응답하기 위해 필요한 이 행동이 객체가 수행할 책임으로 이어지는 것이다.\n객체지향 설계는 시스템의 책임을 완료하는 데 필요한 더 작은 책임을 찾아내고 이를 객체들에게 할당하는 반복적인 과정을 통해 모양을 갖춰간다.\n정보 전문가에게 책임을 할당하는 것만으로도 상태와 행동을 함께 가지는 자율적인 객체를 만들 가능성이 높아진다.\n책임 주도 설계 책임을 찾고 책임을 수행할 적절한 객체를 찾아 책임을 할당하는 방식으로 협력을 설계하는 방법을 책임 주도 설계(Responsibility-Driven Design, RDD)라고 한다.\nWhat is responsibility-driven design?\n책임 주도 설계 방법의 과정\n 시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악한다. 시스템 책임을 더 작은 책임으로 분할한다. 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당한다. 객체가 책임을 수행하는 도중 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할을 찾는다. 해당 객체 또는 역할에게 책임을 할당함으로써 두 객체가 협력하게 한다. 책임 주도 설계는 자연스럽게 객체의 구현이 아닌 책임에 집중할 수 있게 한다.\n구현이 아닌 책임에 집중하는 것이 중요한 이유는 유연하고 견고한 객체지향 시스템을 위해 가장 중요한 재료가 바로 책임이기 때문이다.\n메시지가 객체를 결정한다 메시지가 객체를 선택하게 해야 하는 두 가지 중요한 이유가 있다.\n 객체가 최소한의 인터페이스(minimal interface)를 가질 수 있게 된다. 즉 꼭 필요한 크기의 퍼블릭 인터페이스를 가질 수 있다.\n 객체는 추상적인 인터페이스(abstract interface)를 가질 수 있게 된다. 객체는 무엇을 하는지는 표현해야 하지만 어떻게 수행하는지는 노출해서는 안된다.\n 객체가 충분히 추상적이면서 미니멀리즘을 따르는 인터페이스를 가지게 하고 싶다면 메시지가 객체를 선택하게 한다.\n행동이 상태를 결정한다 객체가 존재하는 이유는 협력에 참여하기 위해서다.\n객체의 행동은 객체가 협력에 참여할 수 있는 유일한 방법이다.\n얼마나 적절한 객체를 만들고 얼마나 적절한 책임을 할당했고 책임이 얼마나 적절한지는 협력에 얼마나 적절한가에 달려있다.\n개별 객체의 상태와 행동이 아닌 시스템의 기능을 구현하기 위한 협력에 초점을 맞춰야 응집도가 높고 결합도가 낮은 객체들을 만들수 있다.\n상태는 단지 객체가 행동을 정상적으로 수행하기 위한 재료일 뿐이다.\n역할과 협력 객체는 협력이라는 주어진 문맥 안에서 특정한 목적을 갖게 된다. 객체의 목적은 협력 안에서 객체가 맡게 되는 책임의 집합으로 표시된다.\n어떤 특정한 협력 안에서 수행하는 책임의 집합을 역할이라고 한다.\n실제로 협력을 모델링할 때는 특정한 객체가 아니라 역할에게 책임을 할당한다고 생각하는게 좋다.\n유연하고 재사용 가능한 협력 역할이 중요한 이유는 역할을 통해 유연하고 재사용 가능한 협력을 얻을 수 있기 때문이다.\n동일한 책임을 수행하는 역할을 기반으로 두 개의 협력을 하나로 통합할 수 있다.\n역할을 이용하면 불필요한 중복 코드를 방지하고 유연한 협력을 만든다.\n책임과 역할을 중심으로 협력을 바라보는 것이 바로 변경과 확장이 용이한 유연한 설계로 나아가는 길이다.\n역할의 구현 역할을 구현하는 가장 일반적인 방법은 추상 클래스와 인터페이스를 사용하는 것이다.\n추상 클래스는 책임의 일부를 구현해 놓은 것이고 인터페이스는 일체의 구현 없이 책임의 집함만을 나열해 놓았다는 차이가 있지만 협력의 관점에서는 둘 모두 역할을 정의할 수 있는 구현 방법이라는 공통점을 공유한다.\n추상 클래스와 인터페이스는 동일한 책임을 수행하는 다양한 종류의 클래스들을 협력에 참여시킬 수 있는 확장 포인트를 제공하고 이들은 동일한 책임을 수행할 수 있는 객체들을 협력 안에 수용할 수 있는 역할이다.\n역할이 다양한 종류의 객체를 수용할 수 있는 일종의 슬롯이자 구체적인 객체들의 타입을 캡슐화하는 추상화라는 것이다.\n객체에게 중요한 것은 행동이며 역할은 객체를 추상화해서 객체 자체가 아닌 협력에 초점을 맞출수 있게 한다.\n객체 대 역할 역할은 객체가 참여할 수 있는 일종의 슬롯이다.\n협력에 적합한 책임을 수행하는 대상이 한 종류라면 간단하게 객체로 간주하고 만약 여러 종류의 객체들이 참여할 수 있다면 역할이라 부르면 된다.\n협력은 역할들의 상호작용으로 구성되고 협력을 구성하기 위해 역할에 적합한 객체가 선택되며 객체는 클래스를 이용해 구현되고 생성된다.\n단순하게 객체로 시작해 반복적으로 책임과 협력을 정제해가면서 필요한 순간에 객체로부터 역할을 분리해내는 것이 가장 좋은 방법이다.\n다양한 객체들이 협력에 함여한다는 것이 확실하다면 역할로 시작하고 정확한 결정을 내리기 어려운 상황이라면 구체적인 객체로 시작하자.\n다양한 시나리오를 탐색하고 유사한 협력들을 단순화하고 합치다 보면 자연스럽게 역할이 그 모습을 들어낼 것이다.\n역할은 객체와 클래스에 비해 상대적으로 덜 알려져 있으며 그다지 주목받지 못한 개념이다.\n대부분의 객체지향 언어들은 역할을 구현할 수 있는 언어적인 편의 장치를 제공하지 않는다.\n그럼에도 유연하고 확장 가능하며 일관된 구조를 가지는 시스템을 구축하는 데 역할은 매우 중요하다.\nObject role modeling\n역할과 추상화 추상화를 이용한 설계가 가질 수 있는 두가지 장점\n 추상화 계층만을 이용하면 중요한 정책을 상위 수준에서 단순화할 수 있다. 설계가 좀 더 유연해 진다. 역할은 공통의 책임을 바탕으로 객체의 종류를 숨기기 때문에 이런 관점에서 역할을 객체의 추상화로 볼수 있다.\n따라서 추상화가 가지는 두 가지 장점은 협력의 관점에서 역할에도 동일하게 적용될 수 있다.\n객체에게 중요한 것은 행동이다. 역할이 중요한 이유는 동일한 협력을 수행하는 객체들을 추상화할 수 있기 때문이다.\n협력 안에서 동일한 책임을 수행하는 객체들은 동일한 역할을 수행하기 때문에 서로 대체 가능하다.\n따라서 역할은 다양한 환경에서 다양한 객체들을 수용할 수 있게 해주므로 협력을 유연하게 만든다.\n배우와 배역 연극 안에서 배역을 연기하는 배우라는 협력 안에서 역할을 수행하는 객체라는 관점이 가진 입체적인 측면들을 훌륭하게 담아낸다.\n협력은 연극과 동일하고 코드는 극본과 동일하다.\n배우는 연극이 시작되면 배역이라는 특정한 역할을 연기한다. 객체는 협력이라는 실행 문맥 안에서 특정한 역할을 수행한다.\n하지만 연극이 끝나면 자신의 배역을 잊고 원래 자기 자신으로 돌아온다.\n객체는 협력이 끝나면 협력에서의 역할은 잊고 원래 자기 자신으로 돌아올 수 있다.\n일반적으러 역할은 객체가 협력에 참여하는 잠시 동안에만 존재하는 일시적인 개념이다.\n따라서 동일한 객체라고 하더라도 객체가 참여하는 협력에 따라 객체의 얼굴은 계속해서 바뀐다.\n객체는 다수의 역할을 보유할 수 있지만 객체가 참여하는 특정 협력은 객체의 한 가지 역할만 바라볼 수 있다.\n","permalink":"https://www.springboot.kr/posts/object/chapter3/","summary":"Chapter 3. 역할, 책임, 협력 객체지향 패러다임의 관점에서 핵심은 역할, 책임, 협력이다.\n애플리케이션의 기능을 구현하기 위해 수행하는 상호작용을 협력, 객체 자체가 협력에 참여하기 위해 수행하는 로직을 책임이라 한다.\n객체들이 협력 안에서 수행하는 책임들이 모여 객체가 수행하는 역할을 구성한다\n객체지향의 본질은 협력하는 객체들의 공동체를 창조하는 것이다.\n클래스와 상속은 객체들의 책임과 협력이 어느 정도 자리를 잡은 후에 사용할 수 있는 구현 메커니즘일 뿐이다.\n역할, 책임 협력이 제자리를 찾지 못한 상태라면 응집도 높은 클래스와 중복 없는 상속 계층을 구현한다고 하더라도 애플리케이션이 침몰하는 것을 구원하지는 못한다.","title":"[오브젝트 OBJECTS] Chapter 3. 역할, 책임, 협력"},{"content":"Chapter 2. 객체지향 프로그래밍 제목처럼 챕터 2에서는 객체지향 프로그래밍에 대해 이야기한다\n협력, 객체, 클래스 객체지향이란 말 그대로 객체를 지향하는 것이다.\n그리고 객체지향에 익숙한 사람이라면 클래스에 대해 고민할 것이고 어떤 속성과 메서드가 들어갈 것인가를 고민할 것이다.\n하지만 이건 객체지향의 본질과는 거리가 멀다.\n위에 말한 것처럼 객체지향은 객체를 지향하는 것이다.\n진정한 객체지향 패러다임으로의 전환은 클래스가 아닌 개체에 초점을 맞춰야 얻을 수 있다.\n그러기 위해서는 우리는 두 가지에 집중해야 한다.\n 어떤 클래스가 필요한지 고민하기 전에 어떤 객체들이 필요한지 고민하기\n 클래스는 공통적인 상태와 행동을 공유하는 것을 추상화한 것. 클래스의 윤곽을 잡기 위해서 어떤 객체들이 어떤 상태와 행동을 가지는지 결정할 것. 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원으로 보기.\n 객체를 협력하는 공동체의 일원으로 바라보는 것은 설계를 유연하고 확장 가능하게 만든다. 객체지향적으로 생각하고 싶다면 객체를 고립된 존재로 바라보지 말고 협력에 참여하는 협력자로 봐야 한다. 객체들의 모양과 윤곽이 잡히면 공통된 특성과 상태를 가진 객체들을 타입으로 분류하고 이 타입을 기반으로 클래스를 구현한다. 이렇게 객체를 중심에 두는 접근 방법을 설계를 단순하고 깔끔하게 만들며 훌륭한 협력이 훌륭한 객체를 훌륭한 객체가 훌륭한 클래스를 만든다.\n도메인의 구조를 따르는 프로그램 구조 소프트웨어는 사용자가 원하는 문제를 해결하기 위해 만들어지게 되는데 이 사용자가 프로그램을 사용하는 분야를 도메인이라 한다.\n객체지향의 패러다임이 강력한 이유는 처음부터 끝까지 객체라는 동일한 추상화 기법을 사용할 수 있기 때문이다.\n요구 사항과 프로그램을 객체라는 동일한 관점에서 볼 수 있기 때문에 도메인을 구성하는 개념들이 프로그램의 객체와 클래스로 매끄럽게 연결될 수 있다.\n일반적으로 클래스의 이름은 대응되는 도메인 개념의 이름과 동일하거나 유사하게 지어야 하며 클래스 사이의 관계도 최대한 도메인 개념 사이에 맺어진 관계와 유사하게 만들어 구조를 이해하고 예상하기 쉽게 만들어야 한다.\n클래스 구현하기 클래스를 구현하거나 다른 개발자에 의해 개발된 클래스를 사용할 때 가장 중요한 것은 클래스의 경계를 구분 짓는 것이다.\n클래스는 내부와 외부로 구분되며 훌륭한 클래스는 이 내부또는 외부의 기능을 공개하고 감출 것에 대한 결정이다.\n그렇다면 왜 클래스의 내부와 외부를 구분해야 할까? 그 이유는 내부와 외부의 경계의 명확성이 객체의 자율성을 보장하고 우리에게도 구현의 자유를 제공하기 때문이다.\n자율적인 객체 우리는 객체를 만들기 전에 두 가지를 알아야 한다.\n 객체가 상태와 행동을 함께 가지는 복합적인 존재라는 것.\n 객체가 스스로 판단하고 행동하는 자율적인 존재라는 것.\n 이 두 가지는 서로 깊이 연관돼 있다.\n객체지향 이전의 패러다임에서는 데이터와 기능이라는 독립적인 존재를 서로 엮어 프로그램을 구성했다.\n하지만 객체지향은 객체라는 단위 안에 데이터와 기능을 한 덩어리로 묶음으로써 문제 영역의 아이디어를 적절하게 표현할 수 있게 했다. (데이터와 기능을 객체 내부로 함께 묶는 것을 캡슐화라고 한다.)\n거기다 객체지향에는 외부에서의 접근을 통제하는 접근 제어도 제공하고 있다.\n객체 내부에 대한 접근을 통제하는 이유는 객체를 자율적인 존재로 만들기 위함이고 그렇게 만들어진 자율적인 객체들을 공동체로 구성하는 것이다.\n캡슐화와 접근 제어는 객체를 두 부분으로 나누다.\n 외부에서 접근 가능한 부분(퍼블릭 인터페이스, public interface)\n 외부에서는 접근 불가능하고 오직 내부에서만 접근이 가능한 부분(구현, implementiation)\n 인터페이스와 구현의 분리 원칙은 훌륭한 객체지향 프로그램을 만들기 위해 따라야 하는 핵심 원칙이다.\n일반적으로 객체의 상태는 숨기고 행동만 외부에 공개하며 어떠한 메서드들이 서브클래스나 내부에서만 접근 가능해야 한다면 protected나 private으로 지정해야 하며 이때 퍼블릭 인터페이스는 public으로 지정된 메서드만 포함된다.\n그 밖의 private 메서드나 protected 메서드, 속성은 구현에 포함된다.\n프로그래머의 자유 프로그래머의 역할을 클래스 작성자와 클라이언트 프로그래머로 구분하는 것이 유용하다.\n클래스 작성자는 새로운 데이터 타입을 추가하고, 클라이언트 프로그래머는 클래스 작성자가 추가한 데이터 타입을 사용한다.\n클래스 작성자는 필요한 부분만 공개하고 나머지는 꽁꽁 숨겨야 하며 클라이언트 프로그래머가 숨겨 놓은 부분에 마음대로 접근할 수 없도록 방지함으로써 걱정하지 않고 내부 구현을 마음대로 변경할 수 있다.\n이를 구현 은닉(implementation hiding)이라 한다.\n이 구현 은닉은 클래스 작성자와 클라이언트 프로그래머 모두에게 유용한 개념이며 클라이언트 프로그래머에게 내부 구현은 신경 쓰지 않고 인터페이스만으로도 클래스를 사용할 수 있기 때문에 부담을 줄일 수 있다.\n또한 인터페이스를 바꾸지 않는 한 외부에 미치는 영향을 걱정하지 않고도 내부 구현을 마음대로 변경할 수 있다.\n그렇기 때문에 클래스를 개발할 때마다 인터페이스와 구현을 깔끔하게 분리하기 위해 노력해야 한다.\n설계가 필요한 이유는 변경을 관리하기 위해서라는 것을 기억해야 한다.\n협력하는 객체들의 공동체 객체지향의 장점은 객체를 이용해 도메인의 의미를 풍부하게 표현할 수 있다는 것이다.\n따라서 의미를 좀 더 명시적이고 분명하게 표현할 수 있다면 객체를 사용해서 해당 개념을 구현하자 비록 하나의 인스턴스 변수만 포함하더라고 개념을 명시적으로 표현하는 것은 전체적인 설계의 명확성과 유연성을 높여준다.\n시스템의 어떤 기능을 구현하기 위해 객체들 사이에서 이뤄지는 상호작용을 협력이라 한다.\n객체지향 프로그램을 작성할 때는 협력의 관점에서 어떤 객체가 필요한지를 결정하고 객체들의 공통 상태와 행위를 구현하기 위해 클래스를 작성한다.\n협력에 관한 짧은 이야기 객체가 다른 객체와 상호작용할 수 있는 유일한 방법은 메세지를 전송하는 것뿐이다.\n다른 객체에게 요청이 도착할 때 해당 객체가 메세지를 수신했다고 한다.\n메세지를 수신한 객체는 스스로 결정에 따라 자율적으로 메세지를 처리할 방법을 결정하며 이 메세지 처리를 위한 자신만의 방법을 메서드라고 한다.\n메세지와 메서드를 구분하는 것은 매우 중요하며 객체지향 패러다임이 유연하고, 확장 가능하며, 재사용 가능한 설계를 낳는다는 명성을 얻게 된 배경에는 메시지와 메서드를 명확하게 구분한 것도 단단히 한몫한다.\n이 두 가지를 구분하는 것에서 다형성의 개념이 출발한다.\n협력 시작하기 객체지향에서 중요하다고 여겨지는 두 가지 개념이 있다.\n 상속(inheritance) 다형성 그리고 그 기반에는 추상화(abstraction)라는 원리가 숨겨져 있다.\n..........................\n디자인 패턴 TEMPLATE METHOD PATTERN[GOF94]\n디자인패턴 - 템플릿 메소드 패턴\nDesign Patterns - Template Pattern\n오버라이딩과 오버로딩 오버라이딩(overriding)\n부모 클래스에 정의된 같은 이름, 같은 파라미터 목록을 가진 메서드를 자식 클래스에서 재정의하는 경우를 가리킨다.\n자식 클래스의 메서드는 오버라이딩한 부모 클래스의 메서드를 가리기 때문에 외부에서는 부모 클래스의 메서드가 보이지 않는다.\n오버로딩(overloading)\n메서드의 이름은 같지만 제공되는 파라미터 목록이 다르다.\n오버로딩한 메서드는 원래의 메서드를 가리지 않기 때문에 이 메서드들은 공존할 수 있다.\n오버라이딩의 예제\npublic class Money { public Money plus(Money amount){ return new Money(this.amount.add(amount.amount)); } public Money plus(long amount){ return new Money(this.amount.add(BigDecimal.valueOf(amount))); } } plus 메서드는 이름은 같지만 파라미터가 다르며 이 경우 두 메서드는 공존하며 외부에서는 둘 다 호출할 수 있다.\n컴파일 시간 의존성과 실행 시간 의존성 어떤 클래스가 다른 클래스에 접근할 수 있는 경로를 가지거나 해당 클래스의 객체의 메서드를 호출할 경우 두 클래스 사이에 의존성이 존재한다고 말한다.\n코드의 의존성과 실행 시점의 의존성이 서로 다를 수 있다. 서로 다를 수 있다는 것은 클래스 사이의 의존성과 객체 사이의 의존성이 동일하지 않을 수 있다는 것이다.\n코드의 의존성과 실행 시점의 의존성이 다르면 다를수록 코드를 이해하기 어려워진다.\n하지만 코드의 의존성과 실행 시점의 의존성이 다르면 다를수록 코드는 더 유연해지고 확장 가능해진다.\n설계가 유연해질수록 코드를 이해하고 디버깅하기는 점점 어려워진다.\n유연성을 억제하면 코드를 이해하고 디버깅하는 것은 쉬워질 수 있지만 재사용 성과 확장 가능성은 낮아진다.\n그렇기 때문에 우리는 항상 유연성과 가독성 사이에서 고민해야 할 필요가 있다.\n차이에 의한 프로그래밍 상속은 객체지향에서 코드를 재사용하기 위해 가장 널리 사용되는 방법이다.\n상속은 기존 클래스를 기반으로 새로운 클래스를 쉽고 빠르게 추가할 수 있는 간편한 방법을 제공한다.\n또한 상속을 이용하면 부모 클래스의 구현은 공유하면서도 행동이 다른 자식 클래스를 쉽게 추가할 수 있다.\n이렇게 부모 클래스와 다른 부분만을 추가해서 새로운 클래스를 쉽고 빠르게 만드는 방법을 차이에 의한 프로그래밍(programming by difference)이라고 한다\n상속과 인터페이스 상속이 가치 있는 이유는 부모 클래스가 제공하는 모든 인터페이스를 자식 클래스가 물려받을 수 있기 때문이다.\n대부분 사람들은 상속의 목적이 메서드나 인스턴스 변수를 재사용하는 것이라 생각하지만 인터페이스는 객체가 이해할 수 있는 메세지의 목록을 정의한다는 것을 기억해야한다.\n자식 클래스는 상속을 통해 부모 클래스의 인터페이스를 물려받기 때문에 부모 클래스 대신 사용될 수 있다.\n컴파일러는 코드 상에서 부모 클래스가 나오는 모든 장소에서 자식 클래스를 사용하는 것을 허용한다.\n이처럼 자식 클래스가 부모 클래스를 대신하는 것을 업캐스팅(upcasting)이라 부른다.\nupcasting-downcasting\n다형성 동일한 메시지를 전송해도 실제로 어떤 메서드가 실행될 것인지는 메시지를 수신하는 객체의 클래스가 무엇이냐에 따라 달라진다.\n이를 다형성이라고 한다.\n다형성은 객체지향 프로그램의 컴파일 시간 의존성과 실행 시간 의존성이 다를 수 있다는 사실을 기반으로 하며 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답할 수 있는 능력을 의미한다.\n따라서 다형적인 협력에 참여하는 객체들은 모두 같은 메시지를 이해할 수 있어야 한다.\n이 말은 인터페이스가 동일해야 하다는 것이고 자식 클래스가 다형적인 협력에 참여할 수 있는 이유는 이들이 부모 클래스로부터 동일한 인터페이스를 물려받았기 때문이다.\n이 자식 클래스의 인터페이스를 통일하기 위해 사용한 구현 방법이 바로 상속이다.\n다형성을 구현하는 방법은 매우 다양하지만 메시지에 응답하기 위해 실행될 메서드를 컴파일 시점이 아닌 실행 시점에 결한다는 공통점이 있다.\n메시지와 메서드를 실행 시점에 바인딩한다는 것이다.\n이를 지연 바인딩(lazy binding) 또는 동적 바인딩(dynamic binding)이라고 한다.\n그에 반해 전통적인 함수 호출처럼 컴파일 시점에 실행될 함수나 프로시저를 결정하는 것을 초기 바인딩(early binding) 또는 정적 바인딩(static binding)이라고 한다.\n객체지향이 컴파일 시점의 의존성과 실행 시점의 의존성을 분리하고 하나의 메시지를 선택적으로 서로 다른 메서드에 연결할 수 있는 이유가 바로 지연 바인딩이라는 메커니즘을 사용하기 때문이다.\n하지만 클래스를 상속받는 것 만이 다형성을 구현할 수 있는 유일한 방법은 아니다.\n인터페이스와 다형성 C#과 자바에서느 인터페이스라는 프로그래밍 요소를 제공한다.\n자바의 인터페이스는 말 그대로 구현에 대한 고려 없이 다형적인 협력에 참여하는 클래스들이 공유 가능한 외부 인터페이스를 정의한 것이다.\n동일한 인터페이스를 공유하는 클래스들은 다형적인 협력에 참여할 수 있다.\n이 경우에도 업캐스팅이 적용되며 협력은 다형적이다.\n추상화의 힘 추상화를 사용할 경우 두가지의 장점을 보여준다.\n 추상화의 계층만 따로 떼어 놓고 살펴보면 요구사항의 정책을 높은 수준에서 서술할 수 있다. 추상화를 이용하면 설계가 좀 더 유연해진다. 추상화를 사용하면 세부적인 내용은 무시한 채 상위 정책을 쉽고 간단하게 표현할 수 있다.\n추상화의 이런 특징은 상위 개념만으로도 도메인의 중요한 개념을 설명할 수 있게 한다.\n추상화를 이용한 설계는 필요에 따라 표현의 수준을 조정하는 것을 가능하게 해준다.\n추상화를 이용해 상위 정책을 기술한다는 것은 기본적인 애플리케이션의 협력 흐름을 기술한다는 것을 의미한다.\n재사용 가능한 설계의 기본을 이루는 디자인 패턴이나 프레임워크 모두 추상화를 이용해 상위 정책을 정의하는 객체지향의 메커니즘을 활용하고 있기 때문이다.\n추상화를 이용해 상위 정책을 표현하면 기존 구조를 수정하지 않고도 새로운 기능을 쉽게 추가하고 확장할 수 있다.(설계를 유연하게 만들수 있다)\n유연한 설계 책임의 위치를 결정하기 위해 조건물을 사용하는 것은 협력의 설계 측면에서 대부분의 경우 좋지 않은 선택이다.\n예외 케이스를 최소화하고 일관성을 유지할 수 있는 방법을 선택해야 된다.\n추상화를 중심으로 코드의 구조를 설계하면 유연하고 확장 가능한 설계를 만들 수 있다.\n추상화가 유연한 설계를 가능하게 하는 이유는 설계가 구체적인 상황에 결합되는 것을 방지하기 때문이다.\n유연성이 필요한 곳에 추상화를 사용하자.\n추상 클래스와 인터페이스 트레이드오프 구현과 관련된 모든 것들이 트레이드오프의 대상이 될 수 있다는 사실이다.\n우리가 작성하는 모든 코드에는 합당한 이유가 있어야 한다.\n아주 사소한 결정이더라도 트레이드오프를 통해 얻어진 결론과 그렇지 않은 결론 사이의 차이는 크다.\n고민하고 트레이드오프하자.\n코드 재사용 상속은 코드를 재사용하기 위해 널리 사용되는 방법이다 하지만 널리 사용되는 방법이라고 해서 가장 좋은 방법인 것은 아니다.\n상속보다는 합성(composition)이 더 좋은 방법이 될 수 있다.\n합성은 다른 객체의 인스턴스를 자신의 인스턴스 변수로 포함해서 재사용하는 방법을 말한다.\n상속 상속은 객체지향에서 코드를 재사용하기 위해 널리 사용되는 기법이다.\n하지만 두 가지 관점에서 설계에 안 좋은 영향을 미친다.\n 캡슐화를 위반한다. 설계를 유연하지 못하게 만든다. 상속의 가장 큰 문제점은 캡슐화를 위반하는 것이고 상속을 이용하기 위해서는 부모 클래스의 내부 구조를 잘 알고 있어야 한다.\n부모 클래스의 구현이 자식 클래스에게 노출되기 때문에 캡슐화가 약화된다.\n그렇게 되면 자식 클래스가 부모 클래스에 강하게 결합되도록 만들기 때문에 부모 클래스를 변경할 때 자식 클래스도 함께 변경될 확률을 높인다.\n결과적으로 상속을 과도하게 사용한 코드는 변경하기도 어려워지게 된다.\n상속은 부모 클래스와 자식 클래스 사이의 관계를 컴파일 시점에 결정한다. 따라서 실행 시점에 객체의 종류를 변경하는 것이 불가능하다.\n상속보다 인스턴스 변수로 관계를 연결한 설계가 더 유연하다.\n합성 인터페이스에 정의된 메시지를 통해서만 코드를 재사용하는 방법을 합성이라고 한다.\n합성을 사용하면 상속이 가지는 두 가지 문제점을 모두 해결할 수 있다.\n인터페이스에 정의된 메시지를 통해서만 재사용이 가능하기 때문에 구현을 효과적으로 캡슐화할 수 있다.\n또한 의존하는 인스턴스를 교체하는 것이 비교적 쉽기 때문에 설계를 유연하게 만든다.\n상속은 클래스를 통해 강하게 결합되는데 비해 합성은 메시지를 통해 느슨하게 결합된다.\n따라서 코드 재사용을 위해서는 상속보다는 합성을 선호하는 것이 더 좋은 방법이다.\n그렇다고 해서 상속을 절대 사용하지 말라는 것은 아니고 상속과 합성을 함께 사용해야 한다.\n","permalink":"https://www.springboot.kr/posts/object/chapter2/","summary":"Chapter 2. 객체지향 프로그래밍 제목처럼 챕터 2에서는 객체지향 프로그래밍에 대해 이야기한다\n협력, 객체, 클래스 객체지향이란 말 그대로 객체를 지향하는 것이다.\n그리고 객체지향에 익숙한 사람이라면 클래스에 대해 고민할 것이고 어떤 속성과 메서드가 들어갈 것인가를 고민할 것이다.\n하지만 이건 객체지향의 본질과는 거리가 멀다.\n위에 말한 것처럼 객체지향은 객체를 지향하는 것이다.\n진정한 객체지향 패러다임으로의 전환은 클래스가 아닌 개체에 초점을 맞춰야 얻을 수 있다.\n그러기 위해서는 우리는 두 가지에 집중해야 한다.\n 어떤 클래스가 필요한지 고민하기 전에 어떤 객체들이 필요한지 고민하기","title":"[오브젝트 OBJECTS] Chapter 2. 객체지향 프로그래밍"},{"content":"Chapter 1. 객체, 설계 챕터의 초반에 소프트웨어 모듈에 대해 이야기한다.\n 실행 중에 제대로 동작하는 것 변경을 위해 존재하는 것 코드를 읽는 사람과 의사소통하는 것 그리고 위에 있는 주제를 가지고 예제를 들어 이야기한다.\n변경에 취약한 코드 객체 사이의 의존성에 관련된 문제, 물론 의존성을 완전히 없애는 것이 아니라 의존성을 낮추며 협력하는 객체들의 공동체를 만드는 것이 좋다.\n이렇게 객체들 간의 의존성이 과한 경우 결합도가 높다고 한다.\n이 결합도가 높을 경우 코드 변경에 있어 어려움을 겪을 수가 있다.\n그러므로 객체 사이의 결합도를 낮추는 것이 좋다.\n그렇기 때문에 설계의 목표는 객체 간의 결합도를 낮춰 코드 수정에 있어 용이한 설계를 만드는 것이다.\n의존성이 높은 코드는 코드를 수정하는 데 있어 어려움을 겪을 수 있다.\n그럼 어떻게 해야 할까? 객체 하나하나를 더 자유롭게 행동할 수 있도록 만드는 것이다.\n자율성 객체 내부에 캡슐화를 사용해자율성을 부여해 주는 것이다. (캡슐화를 통해 객체 내부 접근을 제한한다면 결합도를 낮추며 설계를 좀 더 쉽게 변경할 수 있음.)\n이렇게 되면 좀 더 수정하기 쉬운 객체가 될 수 있다.\n객체에 자율성을 부여하게 되면 의존도가 낮아지게 됨으로 객체 스스로가 문제에 책임을 질 수 있게 된다. (외부로 노출된 기능이 아니기에 내부에서 해결할 수 있어 코드 변경에 있어 유리하다.)\n캡슐화와 응집도 객체 내부의 상태를 캡슐화하고 메시지를 통해 상호작용하도록 만들게 되면 객체 간의 원하는 결과 만을 반환할 것을 알고 있다.\n이렇게 객체 간의 연관된 작업만을 수행하고 연관이 없는 작업은 다른 객체에게 위임하는 객체를 응집도가 높다 한다.\n자신의 데이터를 스스로 처리하는 자율적인 객체는 결합도를 낮추고 응집도를 높인다.\n하지만 응집도를 높이기 위해서는 스스로 자신의 데이터를 책임져야 하기 때문에 자신의 데이터는 자기 스스로 처리할 줄 알아야 한다.\n이렇게 외부의 간섭을 배제하고 메시지를 통해 협력하는 자율적인 객체들의 공동체를 만드는 것이 훌륭한 객체지향 설계를 얻을 수 있는 것이다.\n절차 지향과 객체지향 프로세스와 데이터를 별도의 모듈에 위치시키는 방식을 절차적 프로그래밍이라고 한다.\n절차 지향 프로그래밍에서는 객체들이 수동적이 존재들이다.\n때문에 절차 지향 프로그래밍은 프로세스가 필요한 모든 데이터에 의존해야 하는 근본적인 문제점 때문에 코드의 변경에 취약할 수밖에 없다.\n이러한 문제점을 해결하기 위해서는 자신의 데이터는 자기 스스로 처리할 수 있도록 하는 객체지향 프로그래밍이 효율적일 수 있다.\n그러기 위해서는 데이터와 프로세스가 동일한 모듈 내부에 위치하도록 하고 캡슐화를 이용한 의존성을 적절히 관리해 객체 사이의 결합도를 낮추는 것이다.\n책임의 이동 절차 지향과 객체지향의 근본적인 차이를 만드는 것은 책임의 이동이다. (여기서 책임을 기능을 가리키는 객체지향 세계의 용어로 생각하라 한다.)\n객체지향 프로그래밍은 하나의 객체에 과분한 책임은 분산시켜 결합도를 낮추고 코드 변경에 쉽게 대응할 수 있다.\n객체지향 설계에서는 하나의 객체가 과분한 책임을 가지고 있지 않고 책임을 분산시켜 객체 스스로가 책임을 진다는 것이다.\n이렇게 설계하게 되면 객체지향 애플리케이션은 스스로 책임을 수행하는 자율적인 객체들의 공동체를 구성하게 된다.\n객체지향 설계의 핵심은 적절한 객체에 적절한 책임을 할당이고 어떤 책임을 할당할 것인가 가 중요하다.\n여기서 설계를 어렵게 만드는 것은 의존성이다.\n이것의 해결 방법은 불필요한 의존성을 제거하고 객체 사이의 결합도를 낮추는 것이다.\n불필요한 세부사항은 내부로 캡슐화하여 객체의 자율성을 높이며 응집도 높은 객체들의 공동체를 만들 수 있게 한다.\n거기에 최소한의 의존성만을 남기는 것이 훌륭한 객체지향 설계이다.\n설계가 왜 필요한가 설계와 구현은 떨어트려 이야기할 수 없으며 설계는 코드 작성의 일부이며 코드를 작성하지 않고 검증할 수 없다.\n오늘 완성해야 하는 기능을 구현하는 동시에 내일 쉽게 변경할 수 있는 코드를 짜야 한다.\n좋은 설계란 오늘 요구하는 기능을 온전히 수행하면서 내일의 변경을 매끄럽게 수용할 수 있는 설계이다.\n설계가 중요한 이유는 요구 사항이 항상 변경되기 때문이다.\n객체지향 설계 객체지향 프로그래밍은 의존성을 효율적으로 통제할 수 있는 다양한 방법을 제공하여 요구 사항 변경에 좀 더 수월하게 대응할 수 있게 해준다.\n객체 역시 자신의 데이터를 스스로 책임지는 자율적인 존재이며 코드를 좀 더 쉽게 이해할 수 있게 해준다.\n단순히 데이터와 프로세스를 객체라는 덩어리 안에 넣었다고 해서 변경하기 쉬운 설계가 아니다.\n객체들 간의 상호작용을 통해 구현되고 이 상호작용은 객체 사이에 주고받는 메시지로 표현된다.\n메시지를 전송하기 위해 이런 지식이 두 객체를 결합시켜 이 결합이 객체 사이의 의존성을 만든다.\n훌륭한 객체지향 설계는 협력하는 객체 사이의 의존성을 적정하게 관리하는 설계이다.\n객체 간의 의존성은 애플리케이션을 수정하기 어렵게 만드는 주범이다.\n","permalink":"https://www.springboot.kr/posts/object/chapter1/","summary":"Chapter 1. 객체, 설계 챕터의 초반에 소프트웨어 모듈에 대해 이야기한다.\n 실행 중에 제대로 동작하는 것 변경을 위해 존재하는 것 코드를 읽는 사람과 의사소통하는 것 그리고 위에 있는 주제를 가지고 예제를 들어 이야기한다.\n변경에 취약한 코드 객체 사이의 의존성에 관련된 문제, 물론 의존성을 완전히 없애는 것이 아니라 의존성을 낮추며 협력하는 객체들의 공동체를 만드는 것이 좋다.\n이렇게 객체들 간의 의존성이 과한 경우 결합도가 높다고 한다.\n이 결합도가 높을 경우 코드 변경에 있어 어려움을 겪을 수가 있다.","title":"[오브젝트 OBJECTS] Chapter 1. 객체, 설계"},{"content":"카카오TV 챗봇 만들기\n일 년도 더 넘게 글을 쓰지 않았다. 이제 가끔씩 다시 적어보려고 하는데 시작이 카카오티비 챗봇이다.\n이미 작년에 만들었는데 카카오TV가 죽어서 사용하질 않고 있다.\n시청자가 500명 정도되는 방에서 테스트까지 끝낸 챗봇인데\n테스트가 끝날 때 즈음 사람들이 대부분 트위치로 이동해 사용하질 않았다.\n궁금해하는 사람이 있을 수도 있으니 어떻게 만들었는지 개발기를 적어보도록 해야겠다.\n우선 카카오TV는 트위치 처럼 API를 제공하지 않는다.\n하지만 Web은 제공한다 ([?]팟플레이어라는 플레이어를 사용해 보는사람들이 많다. 웹은 트위치처럼 좋지 않고 기능이적어 사람들이 잘안쓴다.)\n그래서 나는 이 Web을 사용해 챗봇을 제작한다.\n kakao tv : https://tv.kakao.com 카카오티비는 API를 제공하지 않기 때문에 개발자가 유저 ID를 직접 등록해놔야 작동할 수 있다.\n그렇기 때문에 위에 있는 링크에 들어가 검색을 통해 방송하는 사람의 ID 값을 가져와야 한다.\n방송하는 사람의 ID 값을 가져오는 이유는 이 페이지를 계속 가져와 방송의 상태를 확인다.\n확인되면 방송이 켜진 걸 알 수 있고 켜지면 방송에 ID 값이 생긴다.\n그럼 그 ID로 들어간 페이지에서 채팅창의 group id를 가져올 수 있다. (group id를 가져오는 이유는 채팅 소켓에 접속해 채팅창의 내용을 가져올 수 있기 때문)\n그다음으로 방송이 켜지면 셀레니움을 통해 브라우저를 하나 띄우고 실행한다 (https://hub.docker.com/r/selenium/standalone-chrome 일반적인 셀레늄이 아닌 standalone 이다.)\n그리고 카카오 아이디 비번을 입력시키는 동작을 실행해 그 방에 입장한다.\n단, 여기서 알아야 할 것은 제공된 API로 하는 게 아닌 웹으로 하는 것이기 때문에 방송하는 사람과의 얘기를 통해 챗봇의 아이디에 AD(매니저) 권한을 줘야 한다.\nAD의 권한이 아닐 경우 채팅을 보내는 데 제한 시간이 걸린다. 그리고 AD를 줘야 채금이라든지 강퇴를 시킬 수 있다.\n그다음으로 서버단에서 해당방의 채팅방에 소켓을 통해 들어가야 한다.\ngo 언어 resty 라이브러리를 사용해서 요청 사항은 이렇다\nclient := resty.New() resp, err := client.R(). \t//SetHeader(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;). \tSetHeaders(map[string]string{ \t\u0026#34;Content-Type\u0026#34;: \u0026#34;application/x-www-form-urlencoded\u0026#34;, \t\u0026#34;Origin\u0026#34;: \u0026#34;https://live-tv.kakao.com\u0026#34;, \t\u0026#34;Referer\u0026#34;: \u0026#34;https://live-tv.kakao.com/kakaotv/live/chat/user/\u0026#34; + LiveLinkId, // 유저마다 번호가다름 유저번호 \t\u0026#34;Sec-Fetch-Mode\u0026#34;: \u0026#34;cors\u0026#34;, \t\u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36\u0026#34;, \t}). \tSetFormData(map[string]string{ \t\u0026#34;groupid\u0026#34;: Groupid, \t}). \tPost(\u0026#34;https://play.kakao.com/chat/service/api/room\u0026#34;) 이렇게 요청을 보내고 나면 채팅에 접속할 수 있는 ip, roomid 등이 나온다\n그러면 다음으로 거기에 tcp 접속을 시도하면 된다.\n접속을 하고 나면 이제 채팅을 읽을 수가 있다.\n그다음 내가 원하는 명령어를 등록 후 시청자 중 내가 등록해놓은 명령어를 친 경우에 그에 맞게 설정된 메시지를 셀레니움에 들어가 채팅을 칠 수 있는 함수를 만들어 보내면 된다.\n그다음으로 Fiddler를 사용해 카카오 플레이어의 기능 실행 시 퇴장, 채팅 금지 등 내가 원하는 기능들의 API를 가져온다.\n플레이어 내부에서 사용되는 API 목록을 정리한 후 목록에 맞게 기능을 실행하는 함수들을 정의하고 위에 셀레니움에서 가져온 세션들을 사용해 내가 원하는 기능을 만들 수 있다.\n이렇게 해서 기본적인 플레이어의 기능들을 구현한 후 이제 트위치에서 쓰는 싹둑 같은 봇의 기능을 추가 구현해 주면 된다.\n예시로 업타임이나 금지된 단어를 사용 시 경고를 주고 퇴장을 시킨다는 식의 기능들이다. 그러면 채팅 봇의 기능은 구현이 끝난다.\n그리고 방송이 꺼질 경우 셀레니움도 같이 꺼준다. 브라우저 1개당 100MB 정도의 메모리를 먹는다.\n그리고 이 챗봇은 방송하는 사람이 신청을 해서 자기방에 넣는 게 아니라 개발자가 채팅창으로 밀어 넣을 수 있다. (싫어하면 채금 먹고 강퇴당한다.)\n그래서 미리 사전에 얘기를 끝내고 AD를 받고 밀어 넣어야 한다.\nComment) 2021-12-27 추가\n상세한 내용의 채팅 봇을 만드는 방법은 추후에 편을 나눠서 만들도록 하겠습니다.\n","permalink":"https://www.springboot.kr/posts/kakaotv/kakaotv-chat-bot/","summary":"카카오TV 챗봇 만들기\n일 년도 더 넘게 글을 쓰지 않았다. 이제 가끔씩 다시 적어보려고 하는데 시작이 카카오티비 챗봇이다.\n이미 작년에 만들었는데 카카오TV가 죽어서 사용하질 않고 있다.\n시청자가 500명 정도되는 방에서 테스트까지 끝낸 챗봇인데\n테스트가 끝날 때 즈음 사람들이 대부분 트위치로 이동해 사용하질 않았다.\n궁금해하는 사람이 있을 수도 있으니 어떻게 만들었는지 개발기를 적어보도록 해야겠다.\n우선 카카오TV는 트위치 처럼 API를 제공하지 않는다.\n하지만 Web은 제공한다 ([?]팟플레이어라는 플레이어를 사용해 보는사람들이 많다. 웹은 트위치처럼 좋지 않고 기능이적어 사람들이 잘안쓴다.","title":"카카오 TV 챗봇 만들기"},{"content":"2019년 1월에 Google+ API가 종료 됬습니다.\n그래서 찾아보는데\u0026hellip; 찾았습니다.\n물론 너무 짧은 시간동안 서치하고 사용한거라 원래 공유되있는 내용 일수도 있습니다.\n여기로 가면 쉽게 로그인을 사용할수 있는 방법이 있는데요.\n저는 또 사용하지 않았습니다.\n그래서 찾은 방법은 우선 Golang Google OAuth 사용 하기와 겹치는 내용이 있기 때문에 그부분은 스킵하고 바로 진행하겠습니다.\n그부분 이해하시고 참고하여 따라 하시면 됩니다.\n다만 저 포스트에 나와있는 내용중 code를 발급받을때 scope를 https://www.googleapis.com/auth/userinfo.email로 잡아 주시면 됩니다.\n\u0026lt;a href=\u0026#34; https://accounts.google.com/o/oauth2/v2/auth? scope=https://www.googleapis.com/auth/userinfo.email\u0026amp; access_type=offline\u0026amp; include_granted_scopes=true\u0026amp;state=state_parameter_passthrough_value\u0026amp; redirect_uri=http://localhost:3005/googlecallback\u0026amp;response_type=code\u0026amp; client_id=YOUR_CLIENT_ID \u0026#34;\u0026gt; 다음으로 위에 링크된 포스트에 있는 resty를 사용하여 진행 합니다.\n우선 사용자의 정보를 가져오기 위해서는 위의 내용에서 인증을 한뒤 발급받은 코드를 사용해 id_token을 받아야합니다.\n발급을 받기 위해서 위에서 말한 코드를 받는 작업을 해주시면 됩니다.\n다음으로 전에 작성한 포스트에 있는 스코프와 범위가 달르기 떄문에 위에 적어논 범위로 잡고 요청을 보내고 받은 코드를 사용하면 id_token이 들어옵니다.\n요청을 보내면 이렇게 나옵니다!!\n코드를 보겠습니다.\nfunc (this *GoogleService) GetEmail(code string) error { // 기존 token 받는 코드 \tclient := resty.New() \tresp, err := client.R(). \tSetHeader(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/x-www-form-urlencoded\u0026#34;). \tSetFormData(map[string]string{ \t\u0026#34;code\u0026#34;: code, \t\u0026#34;client_id\u0026#34;: CLIENT_ID, \t\u0026#34;client_secret\u0026#34;: CLIENT_SECRET, \t\u0026#34;redirect_uri\u0026#34;: \u0026#34;http://localhost:3005/googlecallback\u0026#34;, \t\u0026#34;grant_type\u0026#34;: \u0026#34;authorization_code\u0026#34;, \t}). \tPost(\u0026#34;https://www.googleapis.com/oauth2/v4/token\u0026#34;) \tif err != nil { \tlog.Println(err.Error()) \t} \tsrc_json := resp.Body() \tvar m map[string]string \terr = json.Unmarshal(src_json, \u0026amp;m) \tif err != nil { \tlog.Println(err.Error()) \t} // 여기 부터는 id_token을 사용해서 google사용자 정보를 가져오는 요청 \tclient = resty.New() \tresp, err = client.R(). \tGet(\u0026#34;https://oauth2.googleapis.com/tokeninfo?id_token=\u0026#34; + m[\u0026#34;id_token\u0026#34;]) \tif err != nil { \tlog.Println(err.Error()) \t} \tsrc_json = resp.Body() \tvar n map[string]string \terr = json.Unmarshal(src_json, \u0026amp;n) \tif err != nil { \tlog.Println(err.Error()) \t} \tlog.Println(n[\u0026#34;email\u0026#34;]) \treturn nil } 위에 주석을 한 부분의 요청 주소를 보시면 \u0026quot;https://oauth2.googleapis.com/tokeninfo?id_token=\u0026quot; + m[\u0026quot;id_token\u0026quot;]로 되있습니다.\n위에서 받은 id_token을 다음 요청 부분에서 넣어주고 GET으로 요청하시면\n짜잔\n이렇게 사용자 정보가 나오는데 여기서 필요한 부분을 슬쩍하시면 됩니다. 저는 email을 슬쩍했습니다.\n댓글 남겨주세요~~\n","permalink":"https://www.springboot.kr/posts/google/golang-google-oauth-signin/","summary":"2019년 1월에 Google+ API가 종료 됬습니다.\n그래서 찾아보는데\u0026hellip; 찾았습니다.\n물론 너무 짧은 시간동안 서치하고 사용한거라 원래 공유되있는 내용 일수도 있습니다.\n여기로 가면 쉽게 로그인을 사용할수 있는 방법이 있는데요.\n저는 또 사용하지 않았습니다.\n그래서 찾은 방법은 우선 Golang Google OAuth 사용 하기와 겹치는 내용이 있기 때문에 그부분은 스킵하고 바로 진행하겠습니다.\n그부분 이해하시고 참고하여 따라 하시면 됩니다.\n다만 저 포스트에 나와있는 내용중 code를 발급받을때 scope를 https://www.googleapis.com/auth/userinfo.email로 잡아 주시면 됩니다.\n\u0026lt;a href=\u0026#34; https://accounts.google.com/o/oauth2/v2/auth? scope=https://www.","title":"Golang Google Oauth Signin 인증 방법"},{"content":"serialVersionUID\n serialVersionUID에 란 무엇인가?\n 객체를 직렬화 하여 역직렬화를 할때 사용한다. 직렬화 외에도 보안등의 용도에 사용됬다. 객체에 대한 고유번호(?)으로 생각하면 된다. serialization가 사용되는 클래스의 경우에 명시적으로 선언해 주는것을 권유한다. 어떻게 사용하는가?\n serialVersionUID를 사용하기 위해서는 static, final, long 타입이여야 한다. private을 추천함. example) private static final long serialVersionUID = 11L; 선언을 하지 않을 경우에는?\n 선언을 하지 않은 경우에는 실행하는 시점에서 serialization을 담당하는 모듈을 통해 자동생성되어 디폴트값이 만들어진다. 그 알고리즘은 Java(TM) Object Serialization Specification에 정의 된 것을 따른다. 디폴트로 생성된 serialVersionUID는 매우 민감하게 반영 하기 떄문에 컴파일러에 따라 Exception이 발생 할 수 있다. Exception 발생 방지를 위해서라도 명시를 해주는 것이 좋다. 참고\n serialVersionUID 선언이유에 대한 포스팅 JAVA serialVersionUID 사용법 serialVersionUID를 선언하는 이유 ","permalink":"https://www.springboot.kr/posts/java/about-serialversionuid/","summary":"serialVersionUID\n serialVersionUID에 란 무엇인가?\n 객체를 직렬화 하여 역직렬화를 할때 사용한다. 직렬화 외에도 보안등의 용도에 사용됬다. 객체에 대한 고유번호(?)으로 생각하면 된다. serialization가 사용되는 클래스의 경우에 명시적으로 선언해 주는것을 권유한다. 어떻게 사용하는가?\n serialVersionUID를 사용하기 위해서는 static, final, long 타입이여야 한다. private을 추천함. example) private static final long serialVersionUID = 11L; 선언을 하지 않을 경우에는?\n 선언을 하지 않은 경우에는 실행하는 시점에서 serialization을 담당하는 모듈을 통해 자동생성되어 디폴트값이 만들어진다.","title":"Java serialVersionUID에 대하여"},{"content":"Golang에서 google photo를 사용하기 위해서 찾았던 내용을 정리 하려고 한다.\n이건 내 방법이고 다른 방법이 있을 수 있음.\nGoogle OAuth 2.0 for Web Server Applications\n우선 위에 있는 사이트를 참조해 정리 한다.\n위 사이트에서는 Go는 라이브러리가 지원 한다고 적혀 있다.\n하지만 쓰지 않았다. ㅋㅋ\n내가 사용한 방법은 resty라는 http 라이브러리를 사용했다.\nresty는 spring으로 보면 resttemplate 같은? 나는 같은 용도로 사용한다.\nresty설치를 위해 다과 같이 입력해 준다.\ngo get -u github.com/go-resty/resty/v2 설치가 끝나면 라이브러리를 사용할수 있다.\n사용법은 README에 상세하게 적혀있다.\n이제 OAuth에 필요한 작업을 하기 위해 본격적으로 만들어 보자\nGoogle Cloud Platform에서 프로젝트 생성과 사용자 인증 정보를 생성해야 한다.\nAPI 및 서비스를 들어가면 다음과 같은 메뉴가 나오는데 거기서 사용자 인증 정보를 눌러 생성하자\n애플리케이션 이름을 적어주고 도메인과 애플리케이션 링크도 적어주자\n작성이 끝나면 다음으로 메뉴에서 사용자 인증 정보를 들어가면 다음과 같이 정보가 생성되 있는데\n정보를 클릭하면 상세 내용을 볼 수 있다.\n이제 여기서 클라이언트 ID(client_id)와 클라이언트 보안 비밀(client_sceret)이 우리가 필요한 정보다.\n이제 OAuth에 권한을 부여 할 아이디를 적용해 주는 단계가 필요하다.\nStep 1: Set authorization parameters을 보게 되면 사용 할 수 있는 파라미터들이 정리 되있다.\nStep 2: Redirect to Google's OAuth 2.0 server를 보면 적용 할 수 있는 방법이 있다.\n인증 정보를 생성할때 만들어 놓은 리다이렉트 url과 client_id가 필요하다.\nscope는 나는 구글 포토를 사용하기 위해서 다음과 같이 범위를 잡았다.\nscope=https://www.googleapis.com/auth/photoslibrary+https://www.googleapis.com/auth/photoslibrary.appendonly\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;SPRINGBOOT.KR\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;a href=\u0026#34; https://accounts.google.com/o/oauth2/v2/auth? scope=https://www.googleapis.com/auth/photoslibrary+https://www.googleapis.com/auth/photoslibrary.appendonly\u0026amp; access_type=offline\u0026amp; include_granted_scopes=true\u0026amp;state=state_parameter_passthrough_value\u0026amp; redirect_uri=http://localhost:3005/redirect\u0026amp;response_type=code\u0026amp; client_id=Your client id \u0026#34; style=\u0026#34;font-size: 50px;\u0026#34;\u0026gt;SPRINGBOOT.KR\u0026lt;/a\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 이렇게 잡아 놓으면 링크를 눌렀을때 권한을 부여할수 있는 페이지로 이동된다.\n그리고 권한을 다 주게 되면 위에서 설정해 놓은 redirect_uri로 code 값이 들어오는데\n그 부분을 캐치해 code를 따로 들어준다.\n나는 ehco를 사용하고 있어서 c.FormValue(\u0026quot;code\u0026quot;) 이렇게 하면 잘 받아 졌다.\n그리고 이제 저렇게 받은 code 값을 사용해 OAuth를 요청 할 수 있다.\nfunc GetAccessToken(code string) { \tclient := resty.New() \tresp, err := client.R(). \tSetHeader(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/x-www-form-urlencoded\u0026#34;). \tSetFormData(map[string]string{ \t\u0026#34;code\u0026#34;: YOUR_CODE, \t\u0026#34;client_id\u0026#34;: YOUR_CLIENT_ID, \t\u0026#34;client_secret\u0026#34;: YOUR_CLIENT_SECRET, \t\u0026#34;redirect_uri\u0026#34;: \u0026#34;http://localhost/redirect\u0026#34;, \t\u0026#34;grant_type\u0026#34;: \u0026#34;authorization_code\u0026#34;, \t}). \tPost(\u0026#34;https://www.googleapis.com/oauth2/v4/token\u0026#34;) \tif err != nil { \tlog.Println(err.Error()) \t} } 위의 코드에서 보면 클라이언트 아이디와 시크릿이 필요하다.\n아까 받은 사용자 인증 정보의 값을 저기에 입력하면 된다.\n그리고 요청을 보내면 다음과 같이 Response가 오면 성공\n{ \u0026#34;access_token\u0026#34;:\u0026#34;1/fFAGRNJru1FTz70BzhT3Zg\u0026#34;, \u0026#34;expires_in\u0026#34;:3600, \u0026#34;token_type\u0026#34;:\u0026#34;Bearer\u0026#34;, \u0026#34;refresh_token\u0026#34;:\u0026#34;1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI\u0026#34; } 그럼 이제 저기에 있는 access token을 api를 사용할때 사용하면 된다!\nexpires_in에 있는 3600은 초단위로 1시간이다.\n그럼 리프레시 토큰은.. 다음 기회에 작성 하겠습니다.\n","permalink":"https://www.springboot.kr/posts/google/golang-google-oauth/","summary":"Golang에서 google photo를 사용하기 위해서 찾았던 내용을 정리 하려고 한다.\n이건 내 방법이고 다른 방법이 있을 수 있음.\nGoogle OAuth 2.0 for Web Server Applications\n우선 위에 있는 사이트를 참조해 정리 한다.\n위 사이트에서는 Go는 라이브러리가 지원 한다고 적혀 있다.\n하지만 쓰지 않았다. ㅋㅋ\n내가 사용한 방법은 resty라는 http 라이브러리를 사용했다.\nresty는 spring으로 보면 resttemplate 같은? 나는 같은 용도로 사용한다.\nresty설치를 위해 다과 같이 입력해 준다.\ngo get -u github.com/go-resty/resty/v2 설치가 끝나면 라이브러리를 사용할수 있다.","title":"Golang Google OAuth 사용 하기"},{"content":"맥북에서 kubernetes 설치를 위해 상단바에 있는 도커를 클릭후\nPreferences\u0026hellip; 를 클릭후 kubernetes 버튼을 눌러\nEnable Kubernetes를 체크 후 apply를 눌러 쿠버네티스를 활성화 시킨다.\n활성화가 되는 도중에는 오른쪽 하단에 Kubernetes is starting 이란 문구가 나오고\n활성화가 끝나면 오른쪽 하단에 Kubernetes is running 으로 나온다.\n다음으로 kubectl을 설치해 주자 kubernetes cli 툴이다.\ncurl -LO \u0026#34;https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl\u0026#34; chmod +x ./kubectl sudo mv ./kubectl /usr/local/bin/kubectl 설치가 끝나면 version 체크로 확인을 해준다.\nkubectl version Kubectl 설치가 끝나면 Minikube 설치를 해준다.\ninstall-minikube 공식 문서\n우선 먼저 가상화 지원 여부를 확인하기 위해 명령어를 입력해준다\nsysctl -a | grep -E --color \u0026#39;machdep.cpu.features|VMX\u0026#39; 명령어를 실행 후\nVMX 이란 녀석에 색으로 강조된 녀석이 있으면 가상화가 활성화 되있는 것 이다.\n다음으로 minikube를 설치 해 주어야 하는데\nhomebrew를 사용하는 방법과 바이너리를 사용해 설치하는 방법이 있다.\n1. brew (1번 방법으로 설치를했을때 에러가나면 1-1의 방법으로 실행해 보자.) brew cask install minikube 1-1. brew install minikube 2. 바이너리 curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64 \\ \u0026amp;\u0026amp; chmod +x minikube 다음으로 minikube를 실행 경로에 추가해 주는 작업이 필요하다.\nsudo mv minikube /usr/local/bin 설치가 완료가 된 후 에는 다음 명령어를 실행해 준다.\nminikube start 입력 후 다음 과같이 설치가 진행되고 실행이 된다.\n그리고 다음과 같이 확인을 해보자\nkubectl get nodes 미니쿠베가 잘 올라가있다.\n","permalink":"https://www.springboot.kr/posts/k8s/kubernetes-install/","summary":"맥북에서 kubernetes 설치를 위해 상단바에 있는 도커를 클릭후\nPreferences\u0026hellip; 를 클릭후 kubernetes 버튼을 눌러\nEnable Kubernetes를 체크 후 apply를 눌러 쿠버네티스를 활성화 시킨다.\n활성화가 되는 도중에는 오른쪽 하단에 Kubernetes is starting 이란 문구가 나오고\n활성화가 끝나면 오른쪽 하단에 Kubernetes is running 으로 나온다.\n다음으로 kubectl을 설치해 주자 kubernetes cli 툴이다.\ncurl -LO \u0026#34;https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl\u0026#34; chmod +x ./kubectl sudo mv ./kubectl /usr/local/bin/kubectl 설치가 끝나면 version 체크로 확인을 해준다.\nkubectl version Kubectl 설치가 끝나면 Minikube 설치를 해준다.","title":"Kubernetes Minikube 설치"},{"content":"Effective Java 3/E 2장 생성자나 열거 탕비으로 싱클턴임을 보증하라 싱글턴은 인스턴스를 하나만 만들수 있는 패턴이다.\n하지만 클래스를 싱글턴으로 만들면 싱글턴 인스턴스를 Mock 구현으로 대체 할 수가 없다\n그렇게 되면 이를 사용하는 클라이언트를 테스트하기가 어려워지게 된다.\n아주 복잡한 직렬화 상황이나 리플렉션 공격에서도 제2의 인스턴스가 생기는 일을 완벽히 막아준다.\n대부분의 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다.\n하지만 만약에 싱글턴이 Enum 외의 클래스를 상속해야 한다면 이 방법은 사용할 수 없다.\n","permalink":"https://www.springboot.kr/posts/effective-java/effective-fava-3e-02-03/","summary":"Effective Java 3/E 2장 생성자나 열거 탕비으로 싱클턴임을 보증하라 싱글턴은 인스턴스를 하나만 만들수 있는 패턴이다.\n하지만 클래스를 싱글턴으로 만들면 싱글턴 인스턴스를 Mock 구현으로 대체 할 수가 없다\n그렇게 되면 이를 사용하는 클라이언트를 테스트하기가 어려워지게 된다.\n아주 복잡한 직렬화 상황이나 리플렉션 공격에서도 제2의 인스턴스가 생기는 일을 완벽히 막아준다.\n대부분의 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다.\n하지만 만약에 싱글턴이 Enum 외의 클래스를 상속해야 한다면 이 방법은 사용할 수 없다.","title":"생성자나 열거 탕비으로 싱클턴임을 보증하라"},{"content":"Effective Java 3/E 2장 생성자에 매개변수가 많다면 빌더를 고려하라 정적 팩터리와 생성자에는 똑같은 제약이 하나 있다.\n선택적 매개변수가 많을때 대응이 어렵다.\n점층적 생성자 패턴도 쓸 수는 있지만 매개변수가 많아지면 클라이언트 코드를 작성하거나 읽기 어려워 진다.\npublic class Hello { private String str; private String str2; private String str3; public Hello(String str) { System.out.println(\u0026#34;Hello str\u0026#34; + str); } public Hello(String str, String str2) { System.out.println(\u0026#34;Hello str1, str2\u0026#34; + str + str2); } public Hello(String str, String str2) { System.out.println(\u0026#34;Hello str, str3 \u0026#34; + str + str3); } public Hello(String str2, String str3) { System.out.println(\u0026#34;Hello str2, str3\u0026#34; + str2 + str3); } } 다음으로 매개변수가 많을 때 활용할 수 있는 자바 빈즈 패턴을 보자.\npublci class Hello { private String str; private String str2; private String str3; public void setStr(String str){ this.str = str; } public void setStr(String str2){ this.str2 = str2; } public void setStr(String str3){ this.str3 = str3; } } 자바 빈즈 패턴에서는 점층적 생성자 패턴의 단점들이 보이지 않는다.\n인스턴스를 만들기 쉽고 그 결과 더 읽기 쉬워졌다.\n하지만 심각한 단점이 있으니 그건 자바 빈즈 패턴에서는 객체 하나를 만들려면 메서드를 여러 개 호출해야 하고 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓인다.\n일관성이 무너지는 문제로 인해 자바빈즈 패턴은 클래스를 불변으로 만들 수 없다.\n하지만 이러한 문제점들에 대한 대안으로 빌더 패턴이 존재한다.\n빌더는 생성할 클래스 안에 정적 멤버 클래스를 만들어두는 게 보통이다.\npublic class Hello { private String str; private String str2; private String str3; public Hello() {} public static class Builder { private String str; private String str2; private String str3; public Builder() { } public setStr(String str) { this.str = str; } public setStr2(String str2) { this.str2 = str2; } public setStr3(String str3) { this.str3 = str3; } public Hello build() { return new Hello(this); } } private Hello(Builder builder) { this.str = builder.str; this.str2 = builder.str2; this.str3 = builder.str3; } } 쓰기 쉽고 읽기 쉬운 빌더 패턴이 나온다.\n빌더 패턴은 파이썬이나 스칼라에 있는 명명된 선택적 매개변수를 흉내 낸 것이다.\n만들어진 빌더 패턴은 다음과 같이 사용할 수 있다.\nHello hello = new Hello.Builder() .setStr(\u0026#34;He\u0026#34;) .setStr2(\u0026#34;ll\u0026#34;) .setStr3(\u0026#34;o\u0026#34;) .build(); ","permalink":"https://www.springboot.kr/posts/effective-java/effective-fava-3e-02-02/","summary":"Effective Java 3/E 2장 생성자에 매개변수가 많다면 빌더를 고려하라 정적 팩터리와 생성자에는 똑같은 제약이 하나 있다.\n선택적 매개변수가 많을때 대응이 어렵다.\n점층적 생성자 패턴도 쓸 수는 있지만 매개변수가 많아지면 클라이언트 코드를 작성하거나 읽기 어려워 진다.\npublic class Hello { private String str; private String str2; private String str3; public Hello(String str) { System.out.println(\u0026#34;Hello str\u0026#34; + str); } public Hello(String str, String str2) { System.","title":"생성자에 매개변수가 많다면 빌더를 고려하라"},{"content":"Effective Java 3/E 2장 생성자 대신 정적 팩토리 메서드를 고려하라 인스턴스를 얻는전통적 수단은 Public 생성자이다.\n 클래스는 별도로 정적 팩터리 메서드를 제공 할 수 있다. (클래스의 인스턴스를 반환하는 단순한 정적 메서드)\n 정적 팩터리 메서드와 팩토리 패턴은 같지 않다.\n 정적 팩터리 메서드가 생성자보다 좋은점 5가지\n 이름을 가질 수 없다.\n 어떤게 더 의미를 잘 설명할 수 있는 가? BigInteger(int, int, Random) BigInteger.ProbablePrime 한 클래스에 시그니처가 같은 생성자가 여러개 필요할 것 같으면 생성자를 정적 팩터리 메서드로 바꾸고 각각의 차이를 잘 드러내는 이름을 지어 주자. 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.\n 덕분에 불변 클래스는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.\n 같은 객체가 자주 요청되는 상황이라면 성능을 상당히 끌어올려 준다.\n 반복되는 요청이 같은 객체르 반환하는 식으로 정적 팩터리 방식의 클래스는 언제 어느 인스턴스를 살아 있게 할지를 철저히 통제할 수 있다. (이런 클래스를 인스턴즈 통제 클래스라 한다.)\n 통제를 하면 싱글턴, 인스턴스화 불가로 만들 수 있으며 인스턴스가 단 하나뿐임을 보장할 수 있다.\n 반환 타입의 하위 타입 객체르 반환할 수 있는 능력이 있다.\n 반환할 객체의 클래스를 자유롭게 선택할 수 있게 하는 엄청난 유연성을 선물한다.\n 인터페이스를 정적 팩터리 메서드의 반환 타입으로 사용하는 인터페이스 기반 프레임워크르를 만드는 핵심 기술이기도 하다.\n 입력 매개변수에 따로 매번 다른 클래스의 객체를 반환할 수 있다.\n 빤환 타입이 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관 없다. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.\n 이런 유연함은 서비스 제공과 프레임워크를 만드는 근간이 된다.\n 서비스 제공자 프레임워크는 3개의 핵심 컴포넌트로 이루어진다.\n 서비스 인터페이스(service interface) 제공자 등록 API(provider registration API) 서비스 접근 API(service access API) 위의 3개의 컴포넌트와 쓰이는 서비스 제공자 인터페이스(service pro-vider- interface)라는 네 번째 컴포넌트가 있다.\n 정적 팩터리 메서드의 단점 2가지\n 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하우ㅢ 클래스를 만들수 없다.\n 정적 팩터리 메서드는 프로그래머가 찾기 어렵다. 그래서 API 문서를 잘 만들어야 한다.\n ","permalink":"https://www.springboot.kr/posts/effective-java/effective-fava-3e-02-01/","summary":"Effective Java 3/E 2장 생성자 대신 정적 팩토리 메서드를 고려하라 인스턴스를 얻는전통적 수단은 Public 생성자이다.\n 클래스는 별도로 정적 팩터리 메서드를 제공 할 수 있다. (클래스의 인스턴스를 반환하는 단순한 정적 메서드)\n 정적 팩터리 메서드와 팩토리 패턴은 같지 않다.\n 정적 팩터리 메서드가 생성자보다 좋은점 5가지\n 이름을 가질 수 없다.\n 어떤게 더 의미를 잘 설명할 수 있는 가? BigInteger(int, int, Random) BigInteger.","title":"생성자 대신 정적 팩토리 메서드를 고려하라"},{"content":"JUnit4 assert method 예시 assertXxx Method 사용 목적 assertArrayEquals(\u0026quot;message\u0026quot;, A, B) 배열 A와 B가 일치함을 확인한다. assertEquals(\u0026quot;message\u0026quot;, A, B) 객체 A와 B가 일치함을 확인한다. B를 파라미터로 A의 equals() 메서드를 호출한다(A.equals(B)). assertSame(\u0026quot;message\u0026quot;, A, B) 객체 A와 B가 같은 객체임을 확인한다. assertEquals 메서드는 두 객체의 값이 같은가를 검사하는데 반해(equals 메서드 사용), assertSame 메서드는 두객체가 동일한, 즉 하나의 객체인가를 검사한다(== 연산자 사용) assertTrue(\u0026quot;message\u0026quot;, A) 조건 A가 참(true)임을 확인한다. assertNotNull(\u0026quot;message\u0026quot;, A) 객체 A가 null임을 확인한다. 더 많은 assert method들 이 있다..\nassertArrayNotEqauls, assertNotSame, assertNotTrue 등\u0026hellip;\n더 상세한 내용은 Assert API 문서 참고..\nAssert Document\n","permalink":"https://www.springboot.kr/posts/junit-01/","summary":"JUnit4 assert method 예시 assertXxx Method 사용 목적 assertArrayEquals(\u0026quot;message\u0026quot;, A, B) 배열 A와 B가 일치함을 확인한다. assertEquals(\u0026quot;message\u0026quot;, A, B) 객체 A와 B가 일치함을 확인한다. B를 파라미터로 A의 equals() 메서드를 호출한다(A.equals(B)). assertSame(\u0026quot;message\u0026quot;, A, B) 객체 A와 B가 같은 객체임을 확인한다. assertEquals 메서드는 두 객체의 값이 같은가를 검사하는데 반해(equals 메서드 사용), assertSame 메서드는 두객체가 동일한, 즉 하나의 객체인가를 검사한다(== 연산자 사용) assertTrue(\u0026quot;message\u0026quot;, A) 조건 A가 참(true)임을 확인한다.","title":"JUnit4 assert method 예시"},{"content":"golang에서 postgresql orm go-pg 사용하기 처음 글에서는 go-pg를 사용한 connection과 close에 대해 적어본다 예제 샘플을 따라하면 잘 될것이다.\n나는 postgresql을 사용하기 위함이 아니라 cockroach db를 go에서 사용하기 위해 go-pg를 선택했다.\ngo-pg에 대한 링크를 걸어 둡니다.\nGithub / Document\n위에 링크를 첨부해 두었다 우선 go-pg에 접속하기 위해서는 다음과 같은 세팅을 잡는다.\n database.go func Connect() *pg.DB { db := pg.Connect(\u0026amp;pg.Options{ User: \u0026#34;user\u0026#34;, Database: \u0026#34;database\u0026#34;, Addr: \u0026#34;addr:port\u0026#34; }) if db == nil { log.Println(\u0026#34;Failed to connect to db\u0026#34;) } else { log.Println(\u0026#34;Connection to db success\u0026#34;) } return db } func Close(db *pg.DB) { closeErr := db.Close() if closeErr != nil { log.Println(\u0026#34;Error while closing the connection, Reason: %v\u0026#34;, closeErr) } log.Println(\u0026#34;Connection closed success\u0026#34;) } 이 설정으로 db에 connect와 close를 할 수 있다.\n다음 편에서는 go-pg를 사용한 select에 대해 알아보도록 하겠습니다.\n","permalink":"https://www.springboot.kr/posts/golang-go-pg-1/","summary":"golang에서 postgresql orm go-pg 사용하기 처음 글에서는 go-pg를 사용한 connection과 close에 대해 적어본다 예제 샘플을 따라하면 잘 될것이다.\n나는 postgresql을 사용하기 위함이 아니라 cockroach db를 go에서 사용하기 위해 go-pg를 선택했다.\ngo-pg에 대한 링크를 걸어 둡니다.\nGithub / Document\n위에 링크를 첨부해 두었다 우선 go-pg에 접속하기 위해서는 다음과 같은 세팅을 잡는다.\n database.go func Connect() *pg.DB { db := pg.Connect(\u0026amp;pg.Options{ User: \u0026#34;user\u0026#34;, Database: \u0026#34;database\u0026#34;, Addr: \u0026#34;addr:port\u0026#34; }) if db == nil { log.","title":"golang go-pg 사용하기"},{"content":"XML TO JSON (AND CUSTOM OBJECT) xml 데이터를 json으로 변환하기 위한 작업이 필요하다.\n그러기 위해서 spring framework에서 많이 사용하는 jackson(jackson-dataformat-xml)을 사용해 변환을 해보려 했다.\n하지만 jackson 라이브러리를 사용해 xml을 json으로 변환할 때 xml의 multiple child 값들이 다 사라지면서 단일 값이 남게 된다.\n그래서 찾아봤다.\n똑같은 현상을 가지고 있는 사람을 발견했다.\n스택오버플로우\n거기에 있는 답변을 참고해보자.\n org.json 라이브러리를 추가시킨다.\n org.json 라이브러리와 jackson 라이브러리를 섞어 사용한다.\n 예제 샘플이다\nimport org.apache.commons.io.IOUtils; import org.json.JSONObject; import org.json.XML; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; ... try (InputStream inputStream = new FileInputStream(new File( \u0026#34;source.xml\u0026#34;))) { String xml = IOUtils.toString(inputStream); JSONObject jObject = XML.toJSONObject(xml); ObjectMapper mapper = new ObjectMapper(); mapper.enable(SerializationFeature.INDENT_OUTPUT); Object json = mapper.readValue(jObject.toString(), Object.class); String output = mapper.writeValueAsString(json); System.out.println(output); } ... 이렇게 하니까 Json 값이 나오게 되며 Object에 값들이 들어가게 된다.\n여기서 커스텀 된 오브젝트를 사용하고 싶다면 Object 자리에 xml -\u0026gt; json으로 컨버터된 custom object를 넣어주면 된다.\n그러면 필요한 xml을 custom obeject안에 담을 수 있게 된다.\n","permalink":"https://www.springboot.kr/posts/java-xml-to-json/","summary":"XML TO JSON (AND CUSTOM OBJECT) xml 데이터를 json으로 변환하기 위한 작업이 필요하다.\n그러기 위해서 spring framework에서 많이 사용하는 jackson(jackson-dataformat-xml)을 사용해 변환을 해보려 했다.\n하지만 jackson 라이브러리를 사용해 xml을 json으로 변환할 때 xml의 multiple child 값들이 다 사라지면서 단일 값이 남게 된다.\n그래서 찾아봤다.\n똑같은 현상을 가지고 있는 사람을 발견했다.\n스택오버플로우\n거기에 있는 답변을 참고해보자.\n org.json 라이브러리를 추가시킨다.\n org.json 라이브러리와 jackson 라이브러리를 섞어 사용한다.\n 예제 샘플이다","title":"자바 XML TO JSON 변환 하기"}]