Browse Category

개발기

Javascript : Prototype 체인 개념의 이해

자신이 익혔다고 알고있는 지식이 제대로 알고 있는지 확인 하고 싶다면 퀴즈를 통하면 간단히 알 수 있다.
Javascript에서 사용되는 prototype 체인 방식을 이해하기 위해 다음에 준비한 간단한 퀴즈를 풀어보자.

  • 모든 문항들은 앞의 질문과 답 이후로 이어진다.
  • 돈(money)과 통장(tongjang)의 프로퍼티를 부여한 지갑jigap 인스턴스를 만들고
  • 제인과 타잔이라는 인스턴스를 prototype으로 지갑(jigap)을 선택해 생성 하였다.

* 제인과 타잔이 프로토타입 체인을 통해 각각의 prototype인 지갑의 프로퍼티인 money와 tongjang을 상속 받게 되었다.

질문 1 : 타잔이 지갑에서 상속받은 프로퍼티 money에서 10을 사용 했다면?

질문2 : 지갑의 프로퍼티 monoey에 10을 더한다면?

질문3 : 제인이 자신의 프로퍼티 tongjang에 10을 꺼내쓴다면?

질문3 : 지갑에 뒤늦게 신규 프로퍼티 card가 추가된다면?

질문4 : 타잔이 신규 프로퍼티 rushcash를 긁어 버렸다면?

질문5 :

개인취향 JPA 사용기 3편 – Scalable Service

안녕하세요? gemini 입니다.

제가 속한 가자고 팀은 매 2주간의 스프린트 마다 추가 기능을 배포하고 있습니다.
아직 오래전 급하게 만든 코드 등 숙제들이 많지만, 많은 부분이 변하여 순조롭게 기능 확장 중입니다.

이번에 제가 공유하려는 내용은 서비스의 기능을 지속적으로 확장시키는 경험 속에서 느꼈던 점을 공유해보겠습니다.
특히 이번 글은 더 개인 취향에 의한 주관적 생각이오니 많은 의견 주시면 좋을 것 같습니다.

먼저 간단한 상황을 예제로 들어보겠습니다.

기획팀에서 아래와 같은 기능 개발 요청이 왔습니다.
1. 유저는 장바구니에 새로운 상품을 추가할 수 있어야 한다.
2. 유저가 장바구니에 담았던 누적 수량, 금액을 저장하고 있어야 한다.

자, 요구 사항을 위해 Business Logic 을 담을 CartService 를 만들어 보겠습니다.

벌써 다 만들었습니다! Cart 추가할 때에 User 를 불러와서 수량 과 금액 을 갱신 시켜 줍니다.

물론 동작에는 이상이 없지만 CartService 에서 User 를 가져와서 제어하는 형태가 제 취향은 아닌 것 같습니다.
테스트 코드 작성하기도 조금 까다로워 보이네요.

일단 제 취향대로 변경하기 위해 User 의 대한 처리를 UserService 가 수행하도록 변경해보겠습니다.

오우, 좋은 것 같습니다. 테스트 코드도 훨씬 작성하기 쉽겠네요
CartService 는 장바구니 저장만 해주고, User 의 대한 내용은 UserService 에게 맡깁니다.

그런데.. 또 다른 요구 사항으로 인해 UserService 에서도 CartService 가 필요한 상황이 발생합니다.
요구 사항을 반영하기 위해 UserService 에서 CartService 를 사용하여 작업을 진행해봅니다.

엇! 구현을 모두 끝내고 서버를 띄워보니 에러가 발생합니다.
Screen Shot 2016-09-19 at 3.02.57 AM
다들 추측하셨겠지만 UserSerivceCartService 가 상호 참조를 하고 있습니다.
이 문제는 다양한 방법으로 해결할 수 있습니다. 일단은 CartService 쪽 의 의존성을 끊어서 해결하겠습니다.

결국은 CartServiceUserRepository 를 통하여 직접 User 를 가져오고 이를 처리하게 되었습니다.
정상 동작할 것이고, 문제 될 것은 당연히 없습니다.

그렇지만 역시 제 취향은 아니네요.
저의 취향대로 Business Object 를 만들어 핵심 로직을 처리하도록 수정해보겠습니다.

이제 CartServiceUserInfoUpdater 를 사용하도록 수정하겠습니다.

이제 CartServiceUserService 와 관계가 전혀 없어졌습니다. 그리고 CartService 에서는 User 에 관하여 어떤 기능만을 사용하는지 더욱 명확해진 것 같네요.

또한 아주 약한 결합을 가지고 있기에 테스트를 작성하기가 아주 편하고 테스트도 설명하는 바가 명확합니다.

제 취향을 강조하기 위해 빡빡한 예를 들어보았습니다. 적절한 예제였는지는 잘 모르겠습니다, 조금 억지가 있어도 양해 부탁드립니다.

꾸준한 기능 확장으로 인해 Service 들에는 수십 개의 Method 가 생길지 모릅니다.
그런 상황에서 Service 들끼리 엉켜 있다면 서로를 어떤 의도로 쓰는지, 또 다른 곳에서는 어떻게 쓰이는지 예측하기가 힘들어지기 때문에 수정하기 힘들어집니다.

심한 경우에는 AService 안에서 B, C, D, E, F, G, H, I, J 서비스들을 가지고 있을 수도 있겠지요.

결론적으로 제가 공유하고 싶었던 내용은 초기에 Service 를 만들고 기능을 계속 확장해가며 너무 커져버린 Service 로 인하여 생기는 문제, 그로 인하여 점점 더 확장해나가기 힘들어지는 상황에 대한 경험 그리고 그것을 개선하기 위한 고민과 개선해본 경험을 공유하고 싶었습니다.

한 Service 가 많은 의존성을 가지고 있고 많은 일을 하고 있다면, 그 Service 가 너무 많은 책임을 지고 있다는 것입니다.

의존성과 일이 많으면 테스트하기가 까다롭고, 테스트하기가 까다로우면 확장하는데 자유롭지 못 합니다.

끝없이 요구되는 추가 기능과 버그 수정을 감당하며 문제없이 배포하기 위해서는
모든 객체들의 역할이 명확하고 테스트하기 쉬운 구조를 만들어서 객체들이 유연한 형태라면
당연하게도 확장에 유리할 것입니다.

부족한글읽어주셔서 감사합니다. 많은 생각들을 공유해주시면 감사하겠습니다. geminikims@gmail.com

AWS EC2에 엘라스틱서치 클러스터 세팅하기

가자고 개발팀은 판매 데이터 분석에 엘라스틱서치 ElasticSearch 사용을 검토하고 있다. 현재는 테스트 목적으로 AWS 서울 리전(region)에 운영되고 있는 두개 EC2 인스턴스에 각각 엘라스틱서치 노드 1개씩 세팅하고 이들(2개 노드)을 클러스터로 묶어서 운영하고 있다.

AWS EC2에 엘라스틱 서치 클러스터 세팅하기

엘라스틱 서치(이하 ES)는 브로드캐스트 방식(같은 대역의 네트워크 주소를 가진 호스트들에게 패킷을 전송하는 방식)을 이용해서 같은 대역의 네트워크에 내에 있는 다른 ES 노드들을 찾아낸다. 브로드캐스트 기능을 이용하면 같은 클러스터 이름을 가지고 있는 ES 노드들이 저절로 클러스터로 묶여 데이터를 분산 처리할 수 있다.

그러나 EC2 인스턴스에서 브로드캐스트가 막혀있기 때문에 클러스터링을 하기 위해선 직접 모든 인스턴스들을 지정해주거나(유니캐스트 unicast 라고 부른다.) 아니면 AWS Cloud 플러그인을 이용해야한다.

이 포스팅에서는 AWS 플러그인을 이용해서 클러스터링 하는 법에 대해 다룰 것이다.

구동 환경 및 버전 정보

  • AWS EC2 인스턴스
    • 1 vCPUs, 2.5 GHz, Intel Xeon Family, 1 GiB memory (t2.micro)
    • Amazon Linux OS
  • ElasticSearch
    • Version 2.3.5

EC2 인스턴스 설정

EC2 인스턴스에 엘라스틱 서치 클러스터를 설정할 때는 다음의 사항이 반드시 지켜져야 한다.

참고: 인스턴스를 하나에 먼저 ES를 세팅해놓고 이미지를 만든 후에 인스턴스를 복제하는 방식으로 다수의 AWS 인스턴스를 쉽게 만들 수 있다.

ES 설치하기

많은 리눅스 패키지가 그렇듯이 ES도 두가지 방식으로 배포가 된다. 한가지는 yum 이나 apt-get 등의 패키지 매니지먼트 시스템을 이용한 방식이고 한가지는 엘라스틱 서치 웹페이지을 통한 압축파일 배포 방식이다.
장단이 있지만, 고급 사용자가 아니라면 패키지 매니지먼트 시스템을 이용한 설치를 권장하며, 이하에서는 ?? 인스턴스 기준으로, yum 으로 설치한 상황을 가정할 것이다. apt-get(우분투)을 사용하는 경우도 크게 다르지 않다.

yum 을 이용해서 설치하기 위해선 ES 리포지토리 등록이 필요하다. 자세한 방법은 Elasticsearch Reference » Setup » Repositories의 YUM 항목을 참조하면 된다.

설치 후에 중요한 파일들의 경로들은 다음과 같다.

참고로 패키지 매니지먼트 시스템을 이용한 설치를 권장하는 이유는 다음과 같다.

  • 패키지 관리의 용이함 : 설치, 업데이트, 삭제 등이 쉬움.
  • 운영의 용이함 : 환경 변수, service 등의 등록을 별도로 하지 않아도 됨.
  • 실행파일, 설정 파일, 로그 파일 등의 표준 경로로 설정됨.

시작 프로세스 등록 (optional)

ES를 AWS 인스턴스가 새로 부팅되었을 때마다 자동으로 시작되게 할 수 있다. 참고로 yum이나 apt-get 으로 설치를 한 경우에는 /etc/init.d/ 에 elasticsearch 라는 이름의 스크립트가 생성되므로

명령만 실행하면 바로 시작 프로세스로 등록이 완료된다.

압축파일을 다운받아서 설치하는 경우에는 /etc/init.d 경로에 elasticsearch 스크립트를 직접 만들어주어야 한다. 이에 대한 내용은 참조.

플러그인 설치하기

ES 플러그인 설치는 plugin install 명령어를 통해 이뤄진다. ES 플러그인 설치시 실행하는 bin 파일은 yum 으로 설치한 경우 /usr/share/elasticsearch/bin 에 위치하고 있다.

일례로 elasticsearch-head, elasticsearch-HQ, cloud-aws 플러그인을 설치하는 경우엔 /usr/share/elasticsearch/bin 디렉토리 내에서 다음과 같이 명령한다.

참고로 위의 플러그인들 중에 cloud-aws 는 EC2 클러스터링을 위해서 반드시 설치해야 한다.

ES 설정하기

AWS ES 클러스터링의 핵심은 설정파일(elasticsearch.yml)에 있다. 클러스터링을 하기 위해서는 설정파일에서 다음의 조건들이 지켜져야 한다.

  • 같은 클러스터로 묶이는 인스턴스들은 cluster.name 이 모두 같은 이름으로 설정되어야 한다.
  • node.name 은 각 인스턴스를 구분할 수 있도록 지어준다.
  • network.host 는 각 ES 인스턴스가 구동되고 있는 AWS EC2 인스턴스의 private IP 를 적어준다.
  • cloud.aws.region 은 ES 인스턴스의 region
  • discovery 속성은 클러스터링을 위해 같은 클러스터 이름 cluster.name 을 가진 ES 인스턴스들을 검색하기 위한 정보들을 설정해준다. 이 때 검색의 범위는 같은 EC2 region 에 속한 EC2 인스턴스로 제한된다.
    • discovery.typeec2로 입력한다.
    • 만약 현재 해당 AWS region에 구동되고 있는 EC2 인스턴스의 개수가 많다면 discovery.ec2 속성에 다음과 같이 입력해주는 것이 좋다. 이는 클러스터링을 위한 노드 검색의 범위를 같은 security group 을 가진 EC2 인스턴스로 제한한다.
    • discovery.ec2.any_group: false
    • discovery.ec2.groups는 해당 EC2 인스턴스의 security group.

즉 예시로, 가자고 ES 인스턴스의 설정파일은 다음과 같이 작성되어 있다. (private IP와 security group ID는 보안상 주석으로 대신했다.

실행 테스트

yum install 을 했다면 /etc/init.d 에 별도의 스크립트를 등록하지 않아도 service 를 이용해서 ES를 시작할 수 있다.

실행이 정상적으로 됐다면 브라우저에서 EC2 public ip 9200 포트로 접속해보자. 즉 http://<YOUR EC2 PUBLIC IP>:9200로 접속해보자.

위와 같이 뜬다면 정상이다. (<…> 에는 해당 ES 노드의 정보가 뜰 것이다.)

클러스터링이 제대로 됐는지 확인하기 위해서 http://<YOUR EC2 PUBLIC IP>:9200/_cat/nodes?v 로 접속해보자. 아래와 같이 클러스터로 묶인 노드들의 정보가 정상적으로 나오는지 확인해보자.

by EastskyKang

개인취향 JPA 사용기 2편 – Entity with Getter,Setter and Test

안녕하세요? gemini 입니다.

지난 1편에서는 기본적인 Spring Boot + Gradle + JPA 설정을 해보고, 기본적인 Entity 를 생성해봤습니다.

오늘은 Entity 의 Getter, Setter 그리고 Test 에 대한 제 개인적인 견해와 활용방법을 공유하려 합니다.

우선 1편에서 다룬 User Entity 를 보겠습니다.

name, phone 필드는 Getter, Setter 를 모두 가지고 있습니다만, id 필드는 Getter 만 존재합니다.

Setter 를 만들지 않은 이유는 id 필드의 수동적 제어를 막기 위함입니다. 애초에 id 필드를 제어 불가로 만들어 혹시 모르는 실수를 막는 것입니다.

또한 기존 코드는 lombok 을 사용하지 않았습니다만.
제 취향대로 User Entity 에 iombok 을 적용해보겠습니다.

우선 저는 다른 기능을 사용하지 않고 @Getter Annotation 만 사용하였습니다,
또한 User Entity 에서 Setter 를 모두 제거하였습니다.

Setter 를 삭제한 이유는 이렇습니다. JPA 는 Transaction 안에서 Entity 의 변경사항을 감지히여 UPDATE SQL을 생성합니다.

즉 Setter 가 Update 기능을 수행하고 있습니다. 이런 상황에서 무분별하게 Setter 를 쓰게 된다면,
예측하지 못한 필드가 Update 가 될 때 Setter 를 일일이 체크해야 합니다.

그럼 Setter 없이 어떻게 데이터 수정을 할까요? 수정 된 User Entity 를 보겠습니다.

updateInfo Method 를 통해서 수정이 가능해졌습니다 어떻게 보면 Setter 가 더 유연해 보일 수도 있습니다. name 만 수정하고 싶을 수도 있기 때문이죠

모든 Entity Method 를 기능화 시키긴 힘듭니다. 그렇지만 생각 없이 @Setter 를 달고 무분별하게 Setter를 사용하는 것은 지양하는 게 좋다고 생각합니다.

이제 앞으로 추가될 모든 Entity의 Primary Key 를 id 필드로 제한하고 싶습니다.

여러 이유가 있지만 기본적으론 PK 를 변경해야 하는 상황이 왔을 때 유연하게 대처하고 싶기 때문입니다.

JPA 는 @MappedSuperclass Annotation으로 Parent Entity Class 를 지원합니다.

그럼, 모든 Entity 의 부모가 될 BaseEntity 를 작성하겠습니다.

추가적으로 모든 Entity 의 생성시간, 수정시간을 기록 할 수 있게 @PrePersist, @PreUpdate Annotation 을 사용했습니다.

이제 User Entity 가 BaseEntity 를 상속받게 구현하겠습니다.

변경된 User 입니다. 자식 Entity 쪽에서는 id 필드가 사라졌으나.

여전히 Id 는 외부 수정이 불가능하고 오직 Persist 시에만 생성됩니다.

다른 팀원들이 BaseEntity를 상속받는 규칙을 지키면 모든 Entity 는 id 필드를 Primary Key 를 가진 형태가 될 것입니다.

자 그럼 조금 더 제 취향에 맞게 변경되었으니, User Entity에 대하여 단위 테스트를 작성해봅시다.

정상적으로 기능이 동작하는 것을 볼 수 있습니다.

위의 테스트는 단순하지만 연관관계가 엮인테스트 또는 id 필드가 반드시 있도록 mocking 해야 하는데
id 필드를 어떻게 설정할 것인가? 이제 와서 Setter 를 추가할 것인가? 아니면 테스트를 하지 않을 것인가?

그럼 이제 test Module 에 아래와 같은 클래스를 만듭니다.

지저분 해 보이기도 하고 그냥 Setter 만들고 싶어질 수도 있습니다. 자 그래도 만들었으니 사용해보겠습니다.

이제 테스트 시에만 id 가 존재하는 Entity 생성이 가능합니다.

저의 경우는 테스트 코드 작성 시 Builder 를 만들어서 사용하는 것을 지향하기에 Builder 내부에서 MockEntity 을 사용할 것 같습니다.

굳이 이렇게까지 id 의 Setter 를 제한할 필요는 없을 수도 있습니다. Setter 쓰는 곳 찾는 게 어렵진 않습니다.

그럼에도 제 생각엔 객체의 행위들은 의미가 있어야 하고, 낭비적 코드를 줄여야합니다.
코드를 간결하고 유의미하게 만드는 것이 가장 중요하다고 생각합니다.

어떤 객체는 모든 필드에 대한 Setter 가 존재해야 할 수도 있습니다.
중요한 것은 무작정 @Setter Annotation 부터 선언하지 않는 것이라고 생각합니다.

두서 없는글 읽어주셔서 감사합니다. 피드백은 언제나 환영입니다. geminikims@gmail.com

**해당 프로젝트 소스는 공유 예정입니다.

– 개인취향 JPA 사용기 1편

개인취향 JPA 사용기 1편 – SpringBoot + JPA + Gradle

안녕하세요? 가자고 개발팀 막내를 맡고 있는 gemini 입니다.

저희 가자고 서비스는 Spring Boot + JPA/Hibernate 기반으로 개발이 되어있습니다.
그리고 개인적으로는 JPA를 쓴지는 약 2년 되어갑니다만, 아직 많이 부족합니다.

팀 내부에서는 JPA에 관하여 여러 얘기가 오고 갑니다만, 저는 ORM 옹호파를 맡고 있습니다.
내부적으로 문제 발생 시 자주 JPA를 의심합니다. 그러나 이것은 JPA를 아직 성숙하게 다루지 못하기 때문이라 생각합니다.

물론 러닝커브가 존재하는 것은 맞기에 팀원들이 JPA 스터디에 힘을 쏟을지, 빠르게 걷어낼지는 팀의 선택입니다.
개인적으로 ORM에 관심이 있어서 가자고를 JPA로 개발해가며 느낀 점 + 제 개인의 취향대로 JPA를 지금 보다 더 올바르게 사용해보려 합니다.

먼저 오늘은 Spring Boot + JPA + Gradle 프로젝트 설정을 해볼 것입니다.

프로젝트 생성

Spring Boot 프로젝트 생성을 진행합니다.
Screen Shot 2016-08-24 at 1.01.50 AM

프로젝트 설정

프로젝트 설명은 편하게 작성합니다. 저는 개인적으로 Maven 보다는 Gradle 을 선호합니다.
각각 장단점이 존재합니다만, 해당 내용은 추후 다른 포스팅으로 정리해보겠습니다.
Screen Shot 2016-08-24 at 1.40.55 AM

초기 의존성 설정

프로젝트 생성 후 필요에 따라 추가하면 되기 때문에 무시하셔도 됩니다만, 전 그냥 눈에 들어오는 것만 체크하였습니다.
Screen Shot 2016-08-24 at 1.04.25 AM

자 이제 프로젝트가 생성되었습니다. 시작이 반인데 벌써 끝나갑니다.

프로젝트 생성 후 build.gradle 을 확인해봅니다. 조금 길고 뭐가 필요한지도 모르겠습니다.
그래서 저는 아래와 같이 수정하였습니다. 모르는 건 일단 지우고 필요할 때 추가하면 될 것 같습니다.

필요에 따라 부가적인 의존성을 추가합니다, Boot 1.4가 릴리즈 되고 Hibernate 버전이 5.0.9.Final 으로 올라왔습니다.
Boot 1.3.x로 설정할 때에는 Hibernate 버전을 강제로 올려줬습니다만, 버전 명시를 생각 없이 늘리다 보면 Boot를 사용한 의미가 퇴색되는 것 같기에 가능한 지양합니다.

application.properties 을 설정합니다, 일단 저는 최소한의 설정을 넣어봤습니다.

Spring Context가 잘 load 되는지 contextLoads 테스트를 돌려봅시다.

Screen Shot 2016-08-24 at 1.53.44 AM

별문제 없이 테스트가 통과하였네요. JPA를 올바르게 쓰는 과정에서 테스트가 중요하다 생각합니다.

앞으로 어떤 것을 만들지 잘 모르겠으나, 장바구니고객 객체가 존재한다고 생각해보겠습니다.
대략 이런 형태로 존재하겠지요.

id 필드는 getter 만 만들었는지, lombok은 몰라서 안 쓰는 건지 궁금하실 수도 있으실 겁니다만
해당 내용은 다음 편에 적도록 하겠습니다.

그럼 이제 Boot를 Run 시켜봅니다.
Screen Shot 2016-08-24 at 11.15.26 AM

정상적으로 동작한 것처럼 보이네요, 다행입니다.

충분히 예상 가능하지만 무슨 일이 벌어졌나 DB Tool을 실행시켜 테이블 목록을 한번 확인해봅니다.

객체만 만들었을 뿐인데 상응하는 테이블이 잘 만들어졌습니다.

몇몇 설정들도 제가 원하는 대로 적용된 것 같네요, 다음 편에서는 이러한 JPA 설정과 객체 설계에 대한 생각을 공유해보겠습니다.

피드백은 언제나 환영합니다. geminikims@gmail.com

두서없는 글 읽어주셔서 감사합니다.