Spring Data JPA를 활용한 DAO를 바꿔보자.

부트 이전에 스프링에서 데이터베이스를 그래도 다른 사람이 쓰는 만큼 쓸려면 MyBatis를 써줘야했다.

MyBatis를 한번이라도 써본 사람이라면 알겠지만 복잡하다. 스프링 XML 설정의 복잡도에 MyBatis의 복잡도를 더하면 상당히 헬 수준으로 올라간다. 단순 목록 하나만 가져오는데 MyBatis를 쓰는건 형식주의에 빠진 불합리의 최상급이었다. 되려 JDBC를 가져다가 prepareStatement에 Bind 변수만 사용하는 것이 오히려 손쉽게 직관적일 수 있다. 글을 읽는 분들중에 Bind 변수를 쓴다는 말을 이해하지 못하시는 분들은 이해의 수고를 덜기 위해 그냥 MyBatis를 쓰는게 정신 건강에 좋다. 하지만 적극 추천한다.

부트(Springboot)를 공부하면서 대부분 Annotation을 가지고 처리하는데 데이터베이스에 대한 접근도 비슷한 방법이 없을까 싶어서 잠깐 찾아봤었지만 역시… JPA 라는 걸 이미 많이 사용하고 있었다.  그리고 MyBatis처럼 설정 그까이게 거의 없다.  테이블 혹은 쿼리와 맵핑되는 설정 몇 가지만으로도 바로 이 기능으로 데이터베이스에서 자료를 읽어내거나 저장할 수 있다.

개인적으로 데이터베이스를 엑세스하는 쿼리를 복잡하게 가져가는건 별로 좋은 방법이 아니라고 굳게 믿는다. 폄하하자면 프로그래밍을 못짜는 개발자들이 논리의 부재를 쿼리로 입막음하려는 경향이 있다.  정말 잘못된 자세다. 정신 머리를 고쳐먹고 쿼리는 최대한 심플하게 작성하고 로직은 프로그램으로 대응하는 버릇을 들이도록 정신을 개조해라.

시작하기

개발을 할려면 먼저 이걸 사용하기 위한 준비부터 해야한다. 메이븐을 개발 환경으로 사용하기 때문에 아래 설정을 반영하면 된다. 데이터베이스 종류에 따라 해당 데이터베이스에 대한 추가적인 의존성을 가져가야한다는건 이미 알고 있으리라 생각한다. 그냥 귀찮으니까 MySQL에 대한 설정 부분만 Copy & Paste하기 좋게 추가해둔다.  그리고 앞으로 설명은 전부 MySQL을 기반으로 진행한다.

<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-jpa</artifactId>
  <version>1.10.1.RELEASE</version>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.6</version>
  <scrope>runtime</scope>
</dependency>

버전에 관련된 사항은 해당 프로젝트 페이지를 통해 확인해보는게 좋다.

라이브러리는 준비됐으니까 이제 데이터베이스를 셋업해봐야겠다. 데이터베이스 셋업은 여기에서 설명하는 개발 주제랑은 무관하니까 일단 스킵. 하지만 개발자라면 꼭 자신의 로컬에 데이터베이스 하나쯤은 실행시켜서 쿼리 도구로 실행해봐야겠다.  그럼 이게 준비됐다라는 전제면 접속을 위한 설정 정보를 코드에 반영해둬야 한다. 이건 보통 application.properties 파일에 다음과 같이 잡아둔다.

spring.datasource.url=jdbc:mysql://localhost:3307/db_name
spring.datasource.username=admin
spring.datasource.password=********
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

응, 근데 JPA가 뭐지?

시작을 하긴 했지만 JPA가 뭐의 약자인지부터 알고 가자. JPA는 Java Persistence API의 약자. 2000년대 초반에 나왔다가 별로 빛을 보지 못했던 것 같다. 스프링 진영에서 아마 iBatis or MyBatis 류의 약진으로 뜰 기회가 아예 없었을 것 같다. 더구나 하나만 고집하는 한국 환경에서 이미 대세가 된 iBatis를 넘어서는 것이 불가능하다.

내가 이해하는 JPA의 컨셉은 데이터베이스에 존재하는 한 모델을 자바의 한 객체로 mapping하는데 목적이 있다.  이 방향성은 단방향성이 아니라 양방향성이다. 즉 코드상에 존재하는 객체는 마찬가지로 데이터베이스에서도 존재해야 한다.  이를 매개하는 존재가 ID 필드이다.  ID 필드는 특정 클래스/테이블의 Uniqueness를 보장하기 위해 사용된다.

jpa-concept

이 ID 필드는 클래스(객체)에 정의되지만 반대 급부로 테이블에도 마찬가지로 해당 필드가 정의되어야 한다. 다만 테이블과 다른 점은 객체를 통해 관리할 정보는 필요한 정보들에 국한될 수 있다.  즉 불필요한 정보는 굳이 객체로 관리할 필요가 없고, 따라서 클래스의 필드로 정의할 필요가 없다. 하지만 테이블 구조에서 Not null 필드 혹은 Constraint에 보호되는 필드라면 적절한 장치가 필요하긴 할 것이다.

단일 테이블부터

테이블 하나에 대한 처리는 정말 쉽다.  하지만 몇가지 지켜야 할 규칙이 있다.  우리가 다뤄야 할 테이블의 이름을 SAMPLE_TABLE이라고 가정하자.

create table SAMPLE_TABLE (
  id int not null autoincrement,
  sample_name varchar(100) not null,
  code varchar(20) not null,
  create_datetime datetime not null,
  primary key(id)
);
  • 테이블의 이름과 동일한 모델 클래스를 만든다. 따라서 클래스는 헝가리안 표기법(Hungarian Notation)을 따라 SampleTable 이라는 이름이어야 한다. 테이블 혹은 필드의 이름이 Underbar(‘_’)로 구분되면 각 단어의 처음이 대문자로 표현되어야 한다. 클래스의 이름을 통해 실제 테이블을 mapping하게 된다.
  • 테이블의 모든 컬럼에 대응하는 모든 필드를 만들 필요는 없으며 ID 필드를 포함한 다뤄야 할 필드들을 정의하면 된다. 쓸데없는 것까지 다룰 필요는 없다.
  • 테이블의 컬럼에 대응하는 필드의 이름은 카멜 표기법(Carmel Notation)을 따라 표기한다.
  • Serialize/Deserialize할 수 있어야 하기 때문에 테이블의 각 필드들에 대해 Getter/Setter를 만들어둬야 한다.  일일히 하기에 귀찮다. lombok 라이브러리를 사용하면 각 필드들에 @Getter @Setter Annotation을 사용하거나 전체 클래스에 대해 적용할려면 @Data Annotation을 적용하면 된다.
  • 스프링에서 해당 클래스를 참조할 수 있도록 @Entity라는 Annotation을 적용한다.
@Entity
@Data
public class SampleTable {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private long id;

    private String sampleName;
    private String code;
}

한 두개 팁을 확인해보자.

  • Entity 클래스와 테이블의 이름이 다른 경우 – Entity 클래스와 테이블의 이름이 틀려지면 @Table(name=”SAMPLE_TABLE”)을 지정한다.
  • 컬럼의 이름과 다른 필드의 이름을 부여하고 싶은 경우 – @Column(name = “COLUMN_NAME”) 어노테이션을 통해 해결한다.
  • 크기가 큰 컬럼들 – 테이블의 컬럼 타입이 CLOB 혹은 BLOB 같은 타입의 경우 자동으로 큰 크기를 처리할 수 없다.  이 경우에 mapping되는 컬럼이  해당 타입인지 여부를 @Clob 혹은 @Blob 같은 어노테이션을 통해 따로 지정해줘야 한다. (생각해보면 당연하다. CLOB/BLOB을 JDBC 혹은 Pro*C로 처리할 때 단순 Variable Binding을 가지고 처리할 수 없다는걸 아는 사람이라면. 그리고 왜 그래야만 하는지도.)

 

CRUD

데이터베이스에 대한 @Entity와 @Data 어노테이션을 활용해 데이터베이스 테이블과 클래스의 맵핑이 완료됐다. 그럼 실제로 동작이 되도록 이 두개를 Repository를 통해 이어주면 된다. 간단하다.

public interface YourSampleTableRepository extends CrudRepository<SampleTable, Long> {
    Layout findOne(Long id);
}

JPA에서 기본 제공해주는 CrudRepository 인터페이스를 상속해서 새로운 당신의 Repository를 만들면 된다. 그럼 기본은 전부할 수 있다. CrudRepository가 뭐하는건지 궁금하지 않은가? 이 인터페이스는 아래와 같이 생겨먹었다.

@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
    <S extends T> S save(S var1);
    <S extends T> Iterable<S> save(Iterable<S> var1);

    T findOne(ID var1);
    Iterable<T> findAll();
    Iterable<T> findAll(Iterable<ID> var1);

    boolean exists(ID var1);
    long count();

    void delete(ID var1);
    void delete(T var1);
    void delete(Iterable<? extends T> var1);
    void deleteAll();
}

인터페이스의 생겨먹은 모습에서 알 수 있듯이 대부분의 동작들을 지원한다. (보기 편이를 위해서 약간 순서를 조정하긴 했다.) 그런데 흔하게 보다보면 CrudRepository라는 것 이외에 JpaRepository도 흔하게 사용한다. 아마도 처음 접하는 예제의 종류가 틀려서 그런가 싶기도 하지만… CrudRepository와 JpaRepository의 차이점은 여기 링크에서 잘 설명하고 있다. 간단히 요약하자면

  • JpaRepository는 CrudRepository의 손자뻘 인터페이스이다.
  • JpaRepository는 Crud에 비해 게시판 만들기에 용이한 Paging개념과 배치 작업 모드를 지원한다.
  • 하지만 다 할 수 있다고 다 이걸로 쓰는건 아니다. 언제나 강조하지만 닭잡는데 소잡는 칼을 쓸 필요는 없지않은가?

다른 길로 빠지긴 했지만 일단 기본으로 쓸려면 그냥 CrudRepository를 기본 칼로 쓰는게 좋다. 상황에 따라 적절한 도구를 사용하고 있는가 혹은 사용할 수 있는 도구를 고를 수 있는 자질이 되는가를 스스로 질문해보자. 답변을 할 수 있는 역량이 본인에게 있다면 다행이다.

Queries

만들고, 변경하고, 지우고 있는지를 확인하고 등등 기본적인 동작이 되는건 대강 확인했다. 그럼 특정 상황에 맞는 조회 문장을 만드는 건 Repository 인터페이스에 조회용 메소드를 정의함으로써 이뤄진다. JPA 환경에서 특정 조건을 만족하는 쿼리를 수행하는 방법은 크게 아래와 같은 가지수로 나뉜다. (상세한 설정에 대한 도움말은 역시 공식 홈페이지에 상세하게 적혀있다.)

  • Query creation from method – 쿼리를 유추할 수 있는 메소드의 이름을 통해 쿼리를 정의한다.  일반적인 규칙은 findBy뒤에 조건이 되는 필드의 이름을 And 혹은 Or 조건을 섞어 작성한다. 실행에 필요한 값들은 언급된 매개 변수의 순서에 맞춰서 작성한다.
public interface UserRepository extends Repository<User, Long> {

  List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
}

이 쿼리가 대강 실행되면 대강 아래와 같은 쿼리의 형식으로 실행된다.

select u from User u where u.emailAddress = ?1 and u.lastname = ?2
  • NamedQuery annotation – 설정을 xml 파일에 두는 경우, 해당 파일의 이름은 orm.xml이어야 한다. 그게 아니면 테이블에 맵핑된 클래스(이걸 Domain Class라고 부르는군.)에 @NamedQuery라는 annotation을 사용해서 실행될 쿼리를 아래 예제처럼 적어주면 된다.
<named-query name="User.findByLastname">
  <query>select u from User u where u.lastname = ?1</query>
</named-query>
@Entity
@NamedQuery(name = "User.findByEmailAddress", query = "select u from User u where u.emailAddress = ?1")
public class User {
    ...
}

public interface UserRepository extends Repository<User, Long> {
  List<User> findByLastname(String lastname);

  User findByEmailAddress(String emailAddress);
}
  • Query annotation – 가장 흔하게 사용하는 방법이다. Repository의 조회 메소드에 직접 실행될 쿼리를 적어준다.
public interface UserRepository extends JpaRepository<User, Long> {
  @Query("select u from User u where u.emailAddress = ?1")
  User findByEmailAddress(String emailAddress);
}

JPA는 조건문에 들어가는 파라미터의 위치를 메소드의 파라미터 위치로부터 유추한다. 즉 ?1 이라는 표시된 곳에 첫번째 파라미터의 값이 반영된다. 쿼리가 복잡하다면 이름을 참조하는 좀 더 알아보기 쉬운 방식으로 사용하는게 좋다. 이름을 참조할려면 인터페이스 메소드의 각 파라미터 앞에 @Param 이라는 추가 annotation을 사용해서 참조 가능한 이름을 주고 쿼리에서 이 이름을 사용하면 된다.

public interface UserRepository extends JpaRepository<User, Long> {
  @Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
  User findByLastnameOrFirstname(@Param("lastname") String lastname, @Param("firstname") String firstname);
}

예제에서 보면 쿼리에 firstname, lastname이라는 변수를 참조했고, 참조된 변수는 @Param annotation으로 메소드에 함께 선언된 것을 확인할 수 있다.  복잡하다면 이렇게 해주는게 읽는 사람을 위한 배려다.

그래도 테이블 두개는 조인할 수 있어야지

테이블 한개에 대한 처리는 지금까지의 설명으로 충분하다. 하지만 2개 이상의 테이블을 조인해서 뭔가를 추출하는 경우라면 어떻게 해야할까?  쿼리상으로는 간단히 조인을 걸어서 결과를 확인하는 아주 간단한 거다. JPA 방식에서 사용할려면 좀 더 터치가 필요하다.  간단히 다음과 같은 테이블 구조가 있다고 하자.

joined

여기에서 사용자쪽의 정보를 바탕으로 그룹에 대한 정보를 가져올려고 한다면


select user.userid, password, group.groupid, ... from user join group on user.group_id = group.group_id

와 같이 하면 된다.  이걸 JPA 기반에서 처리한다면


@Entity
@Data
public class User {
    ...
    @ManyToOne
    @JoinTable(name="USER_GROUP")
    Group group;
};

@Entity
@Data
public class Group {
};

흠.. 이렇게 하면 될까? 실제로 해보질 않아서 모르겠다.

JPQL이라는 걸 JPA 내부적으로 사용한다고 한다. 하지만 파보질 않아서 잘 모르겠네.

상세한 내용은 다음 링크에서 도움을 나중에 받을 수 있을 것 같다.

  • https://en.wikibooks.org/wiki/Java_Persistence/Querying
  • http://www.objectdb.com/java/jpa/query/jpql/from

일단 미완.

Tips

MySQL의 경우에 테이블 이름을 Underscore(_)로 구분하는게 아니라 대문자 형식(ex: MyTable)으로 작성하는 경우가 있다. 이 경우에는 Entity의 테이블 이름을 MyTable로 주면 Spring JPA에서 자동으로 my_table로 변경해버려서 테이블을 찾지 못한다고 이야기하는 경우가 있다.  이때는 Naming Strategy를 아래와 같이 변경해주면 된다.

spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

한참을 헤메긴 했지만 알고 있다면 좋을 것 같다.

5월에 읽은 책 – 김훈 작가의 “라면을 끓이며”

한달에 만화책과 무협지를 빼고 뭐라도 두 권은 읽자라고 시작해서 처음으로 읽기를 마친 책.  읽기를 시작한지는 한달쯤 되었던 것 같긴 하지만 최근들어서 텍스트를 읽는 것이 어렵게 다가온 적이 요즘만하게 없었던 것 같다. 오래 걸리고 또 의미를 삼키기 어렵다.

김훈 산문집 – 라면을 끓이며

 

굳이 독후 감상문을 적을 필요는 없겠다.

본래 스스로 그러한 것들을 향하여 나는 오랫동안 중언부언하였다. 나는 쓸 수 없는 것들을 쓸 수 있는 것으로 잘못 알고 헛된 것들을 지껄다. 간절해서 쓴 것들도 모두 시간에 쓸려서 바래고 말하고자 하는 것은 늘 말 밖에 있었다. 지극한 말은, 말의 굴레를 벗어난 곳에서 태어나는 것이다.

이제 함부로 내보낸 말과 글을 위추치는 일을 여생의 과업으로 삼되, 뉘우쳐도 돌이킬 수 없으니 슬프고 누추하다. 나는 사물과 직접 마주 대하려 한다.

비슷한 동경되는 마음이 있어서 옮겨봤다.

글에도 색이 있구나라는 것을 처음으로 알게해준 작가가 김훈 작가가 아닌가 생각든다.  이전에 남한 산성에서 글에도 맛이 있구나라는 걸 느꼈는데 기대했던 맛과는 다른 글의 맛을 이번 책에서는 느낄 수 있었다.  아마도 세월의 흐름과 산문의 특성이 있어서 그런게 아닐까 하는 어림없는 추측을 던진다.  집에 칼의 노래도 있었던 것 같은데 올해가 가기전에 이 책도 한번 경험해봐야겠다.

Amazing Lamda

Java 8에서 제대로 지원하는 stream과 람다(lamda)를 섞어서 쓰면 좋은데 람다가 코드를 어느 수준까지 줄여주는지를 한번 살펴보니 무시무시하다라는 생각이 든다.

public class OneLoginSimpleUserService {
    public UserAuthority processLogin(Authentication auth) {
        return new UserAuthority() {
            @Override
            public Collection<? extends Authority> getOwnedRoles() {
                return Arrays.asList(new Authority[]{ new Authority() {
                    @Override
                    public String toStringCode() {
                        return"ROLE_ADMIN";
                    }
                }});
            }
        };
    }
}

이걸 람다를 적용해서 고치면…

public class OneLoginSimpleUserService {
    public UserAuthority processLogin(Authentication auth) {
        return () -> Arrays.asList(new Authority[] { () -> "ROLE_ADMIN" });
    }
}

기존 자바의 인터페이스를 사용해서 불필요한 라인들이 딱 한줄의 return문으로 정리된다.

이걸 해석하는데 좀 시간이 필요하긴 한데, 얻을 수 있는게 참 많을 것 같다. 특히나 Interface를 활용해서 DI 기법으로 코드를 작성하는 방식이 더욱 더 각광받을 것 같다.

미국과 다르지 않은 한국 개발자 환경?

페북에 개발자의 한국 상황과 미국 상황을 비교한 슬라이드가 올라와서 또 열심히 까는구나 하는 생각이 들어서 봤더니, 왠걸?  이 친구는 많이 다르지 않다고 이야기를 하네. 헐…

살짝 이력을 살펴보니 LG좀 다니다가 라인좀 다니다가 MS로 이직한 친구네!!

좋은 회사(?)들을 잘 거쳐서 성공적으로 미쿡 회사에 안착을 한 모양이다.  근데 좋은 대기업만 다녀서 그런가 한국의 다른 개발자들에 대해서 잘 모르는 모양이다.(네이버나 카카오를 중견기업이라고 이야기하는 개발자는 근래에 처음보긴 했다.)

이런 친구들에게 현실을 이야기하면

하면 되는데 너는 왜 못해? 다 너 잘못임!!

ㅎㅎㅎ 간단히 그럼 현실을 살펴볼까?

누구나 다 공감하는 건 역시 돈이다. 일이라는게 일차적으로 생활을 영위하기 위함이니까. 과연 일반적인 한국의 개발자들의 벌이는 어떤지 함 살펴보자. 우리나라의 2015년 기준 소프트웨어 개발자 단가가 이러하다고 정부에서 이야기한다.

개발자단가

평균적인 개발자를 대강 경력 5, 6년차 정도라고 생각해보면, 기준이 “중급 기술자“인 것 같다.  표에서 확인해보니 한달 평균 임금이 470만원이 조금 안된다네. 흠… 470만원.  많이 준다고 치면 한달에 400정도 받고 이걸 연봉으로 환산해보면 대략 5,000만원쯤 되겠네.  잘쳐주는 회사의 신입으로 들어가면 3,000만원쯤으로 시작하니까 대략 1년에 400만원씩 올라야 하고 이정도면 연봉 인상률이 대략 10%가 되야지 말이 되는 이야기인 것 같군.

10% 연봉 인상율이면… 말이 안되게 능력자이면 모를까 대한민국을 살아가는 평균 개발자는 절대 이렇게 살 수 없다.

올라가는 연봉보다는 약값이 더 들어간다.  아니 약값보다는 어디가 하나 제대로 고장난다.

한국에서 개발자의 안정적인 처우를 보장하는 회사는 몇 개 회사정도밖에는 되지 않는다.  아마도 그 친구가 이야기했던 네이버나 카카오 혹은 최근에 뜨는 소셜커머스 업체 정도가 되어야 가능하지 않을까 싶다.  간단히 검색해보면 2015년 기준으로 대졸 초임이 3,500 이상이고 인센티브를 합치면 입사 첫해에 받는 금액이 4,000 만원 이상이다.  처음 차이가 1,000만원이겠지만 이 금액은 시간이 흐르면 흐를수록 더 벌어지게 된다.  당연이 이 정도의 금액이라면 생활하는데 거짐 불편함이 없다.

네이버, 카카오 직원이 아닌 대한민국의 일반적인 개발자들은 이 차이를 메우기 위해서 열심히 야근을 한다. 하지만 소위 말하는 이런 류의 회사들에 있는 일반적인 개발자들과의 차이는 시간이 가면 갈수록 벌어진다. 왜냐구?  PT 자료에서 미쿡 친구들과 사용하는 기술들이 크게 다르지 않다고 이야기를 했다.  그렇다. 이 회사들에서 사용하는 기술들에는 큰 차이가 없다. 이런 걸 챙기는 별도의 조직이 있을 뿐만 아니라 생각외로 이런 걸 할 시간이 좀 된다.  젠킨스,  CI, Git  그리고 docker와 같은 도구들을 활용하기 위한 환경들을 누군가가 앞서서 조사하고 셋업을 해놓으면 그 뒤를 따라가는 사람들은 큰 비용을 들이지 않더라도 써보고 느껴볼 수 있다.

하지만 SI 개발 현장에 있는 일반적인 개발자들에게 이런 기술들을 익혀서 사용할 수 없다. 턱없이 시간이 부족하다. 해야만 하는 코딩을 마무리하기에도 시간이 부족한데 이런 편이 기능이나 유틸들을 제대로 즐길 시간이 없다. 그리고 이런 기능들을 설치해서 활용해볼 만한 인프라 환경도 열악하다.  처음부터 끝까지 본인 혼자서 감당해야 한다.  물론 함께할 수 있는 동료가 있다면 이런 부담을 덜 수 있겠지만 그런 친구… 흔하지 않다.

최근에야 AWS와 같은 저렴한 클라우드 인프라가 있기 때문에 접근성이 좋아지긴 했지만 절대적 시간 부족은 어떻게 할 수 있는게 아니다.

이러다보면 닭이 먼저냐 달걀이 먼저냐에 대한 쓸데없는 논쟁으로 빠져들게 마련이다. 실력을 키울려면 본인이 알아서 해야하는거 아니냐와 시간이 없는데 어떻게 그런걸 하느냐와 같은… 일반 개발자들에게는 시간이 부족하고 삶이 있는 저녁은 그림의 떡이다.

 

 

 

Maven을 이용해서 신규 프로젝트 만들기

한땀한땀 손으로 Maven 프로젝트를 만드는 것도 의미있는 일이지만 귀찮다.

mvn archetype:generate -DinteractiveMode=false -DarchetypeArtifactId=maven-archetype-quickstart \
-DgroupId={project-packaging} -DartifactId={project-name}

와 같은 형태로 잡아주면 된다.
최근 개발은 Spring Boot를 많이 이용하기 때문에 여기에서 주로 쓸만한 archetype들을 나열해보면

  • maven-archetype-quickstart
  • spring-boot-sample-simple-archetype
  • spring-boot-sample-data-jpa-archetype
  • spring-boot-sample-actuator-log4j-archetype

Spring에서 사용할 수 있는 전체 Archetype 목록은 여기에서 확인 가능하다.  다만 Spring 기반으로 프로젝트를 생성시킬려면 기본 archetypeArtifactId 이외에 archetypeGroupId=org.springframework.boot 값을 추가로 줘야한다.

mvn archetype:generate -DinteractiveMode=false \
-DarchetypeGroupId=org.springframework.boot -DarchetypeArtifactId={spring-archetype} \
-DgroupId={project-packaging} -DartifactId={project-name}

가장 대표적인 API 개발용 명령을 이용하는게 가장 깔끔하다.

mvn archetype:generate -DinteractiveMode=false \
-DarchetypeGroupId=org.springframework.boot -DarchetypeArtifactId=spring-boot-sample-simple-archetype \
-DgroupId={project-packaging} -DartifactId={project-name}

그 다음에 생성된 pom.xml 파일의 spring-boot-starter-parent 의 버전을 1.3.3.RELEASE로 변경한다. CORS 지원과 몇가지 기능을 사용할려면 이 이상 버전을 사용하는게 좋다. 추가적으로 다음의 Dependency들을 pom.xml에 반영하면 즐거운 코딩 생활에 도움을 얻을 수 있다.

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.8</version>
    </dependency>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.4</version>
    </dependency>
    <!-- for db programming with mysql -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
      <scope>runtime</scope>
    </dependency>

한가지를 더 추가하자면 람다등을 사용할려면 자바 컴파일 환경을 1.8 이상으로 설정하는게 편하다. 다음 빌드 플러그인을 설정해두면 대부분의 IDE에서 인식한다.

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>

즐 코딩~

 

JUnit Parameterized Test – 반복 테스트를 하는 뻔한 방법

JUnit에서 조건값을 바꿔가면서 테스트를 해야하는 경우에 이 방법을 사용하는게 테스트 비용을 아끼는데 좋다.

@RunWith(Parameterized.class)
public class PrimeNumberCheckerTest {
    private int inputNumber;
    private boolean expectedResult;
    private PrimeNumberChecker checker;

    @Before
    public void setup() {
        checker = new PrimeNumberChecker();
    }

    @Parameterized.Parameters
    public static Collection<PrimeNumberValidationInput> primeNumbers() {
        return Arrays.asList(new PrimeNumberValidationInput[] {
                new PrimeNumberValidationInput(2, true),
                new PrimeNumberValidationInput(6, false)
        });
    }

    public PrimeNumberCheckerTest(PrimeNumberValidationInput input) {
        this.inputNumber = input.number;
        this.expectedResult = input.expectation;
    }

    @Test
    public void testCheckerValidatePrimeNumber() {
        assertThat(checker.validate(inputNumber), is(expectedResult));
    }
}

예제에서는 테스트를 위한 입력으로 PrimeNumberValidationInput이라는 클래스를 사용했다. 다른 책에서 예제를 인용할 때는 보통 Object[][]을 이용하는 경우가 많던데, 물론 간단한 수치 입력을 하는 경우에는 이 방법을 사용해도 크게 나쁘지는 않은 것 같다. 하지만 Object 변수를 사용한다는게 뭔가 테스트 코드의 품질을 떨어트리는 것만 같은 후질근함을 내보하기 때문에 별로 좋아보이지는 않는다. 더구나 코드의 의미 부여적인 측면에서 각 변수의 이름을 살려주는게 테스트의 맥락에서 좀 더 좋아보이기 때문에.

    public static class PrimeNumberValidationInput {
        public int number;
        public boolean expectation;

        public PrimeNumberValidationInput(int number, boolean expectation) {
            this.number = number;
            this.expectation = expectation;
        }
    }

이런 클래스 하나쯤 테스트 코드에 심어주면 간지나지 않을까 싶다. 테스트를 위해 투자하는거 넘 인색하지 말자. 테스트도 관리해야할 코드 가운데 하나니까 말이다.

Thunderdome – 개발자의 재미, 개발하기

2주일간의 출장을 마치고 복귀했다.

몇번 가지도 않았지만 뱅기타고 가는 출장에서는 풀어내야할 꺼리들이 한가득이었다.  그리고 시간도 부족했다.  일에 대한 압박감도 있고, 제대로 되지도 않는 영어로 이야기를 하자니… 출장가서 잠을 제대로 못자는건 단순히 시차 적응 실패가 가장 큰 원인이겠지만 이런 류의 스트레스도 한몫을 한다고 본다.

이번 출장은 좀 다른 목적을 가지고 있었다.  썬더돔(Thunderdome)이라는 회사 내부에서 하는 핵커톤 행사에 참가하고, Architecting Conference에 참가해서 배움을 넓히는 시간이었다. 한마디로 개발자스럽게 놀다가 오는 시간이랄까? 하지만 피곤한건 변하지 않는다.

thunderdome8-after

처음 시작하는 모습은 그럭저럭 괜찮긴 했지만, 해커톤 행사 하루만에 확연하게 거북목 증세를 보인다.  거북목을 피할려고 받침대랑 키보드랑 마우스도 바리바리 챙겨갔음에도 불구하고 이런 자세가 되는건 아무래도 병인 것 같다.

thunderdome8-before

이런 상태로 3일을 정말 열심히 코딩을 했던 것 같다.  물론 팀의 성과는 좋았다. 다만 내가 기여할려고 했던 부분은 제대로 돌리는데 실패했다. 너무 자신만만하게 문제를 대했던 것이다.   덕분에 내부 구조가 어떻게 돌아가고 뭐가 문제인지는 확실하게 알게 됐지만… 이걸 제대로 만드는건 3일동의 작업만으로는 택도 없다라는 진실을 깨우쳤다.

하지만 같이 간 다른 친구들이 정말 엄청나게 일을 잘 해줬다. 덕분에 원래 의도에 거의 부합하는 작품을 만들어냈다.  시연 시간에는 덕분에 사람들의 관심이 폭발해서 참가한 보람을 많이 찾을 수 있었다. 3일 동안 어떻게든 재미있게 개발을 했던 시간이 나름 유종의 미를 거두니 잠못잔 시간이 아깝게 느껴지지는 않았다.

thunderdome8_converted

나중에 제대로 만들어진다면 그때 제대로 기여할 수 있지 않을까 하는 생각이다.

LA에서 일정은 정말 피곤한 3일을 보내는 것으로 마무리했다. 한인 타운에서 한잔 술 털어넜고 같이 했던 친구들과 재미있는 이야기도 나누니 정말 좋았다.  이런 좋은 시간을 뒤로하고 뉴욕으로 컨퍼런스 들으러 이동했다.

혼자 LA에서 NYC로 이동하는 것도 또 다른 하나의 도전이다. 혼자 출장온 경험도 있긴 했지만 그때는 대한항공을 타고 오니 낯설음이라는게 적었다. 하지만 AA 항공타고 미국 국내선으로 이동하는 길에 한국인은 딱 나 혼자였다.  혼자서 이런 저런 일들을 다 처리해야한다고 생각하니 정말 깝깝스러웠다.

가장 큰 문제는 이야기할 사람이 없다는거.  회사에서는 그래도 업무적으로 이야기할 사람이라도 있었고, 지역 오피스를 배려하는 좋은 분위기가 있어서 이야기는 것도 훨씬 수월했다. 컨퍼런스에서 막다뜨린 사람들과 뭐라도 한마디 말을 섞어볼려고 해도 언어적인 장벽이 전혀 만만하지 않았다. 기술 언어를 하면 될거라고 생각했지만 사람들은 생활 영어를 한다는거.  이런게 또 다른 Pain Point!!

대강 알아들으면서 대강 이야기를 하면서 여기서도 4일간 고생을 했다.  2일은 Traning 세션이었고 2일은 Conference였는데 나름 의미는 있었다. 트레이닝 세션으로 들었던 Docker 관련 사항은 회사의 시스템 방향이기도 했기 때문에 짧게라도 Docker를 경험해볼 수 있는 좋은 시간이었다.   물론 따로 공부할려고 책도 읽는 중이었지만 단순히 눈으로 읽는 것과 실제로 손으로 타이핑을 쳐보는 건 완전히 다른 경험이니 말이다.

사진에 있는 스트라이브를 입으신 분이 주로 Docker를 이용한 Architecting에 대해 강의를 진행했는데 영국 억양이 상당히 인상적이었다.  삐쩍 말랐을 거라고 생각했는데 와중에 올챙이 배를 봤을 때는 웃음이 절로 나왔다.  역시 나이를 먹으면 나오는게 똥배구나…

다행이 맨하튼에 한인 타운이 있어서 밥은 정말 잘 먹었다. LA에서 먹는 버거나 피자 혹은 현지 음식들은 상당히 소금을 많이 쳐서 별로 땡기지 않았는데, 뉴욕은 음식이 짜지 않았다. 🙂 이것만으로도 정말 다행이었다.  물론 저녁은 항상 한식이었지만.

wp-1461337451094.jpeg

대도시의 백미는 역시 네온사인이었던 것 같다. 영화나 미드에서 종종 나오던 골목을 실제로 걸을 수 있었던 것만으로도 좋았던 경험이지 않았을까 싶다.

git의 ssh key 설정하기

추가하는 방법은 git의 사용자 셋팅에 SSH Key추가하기를 하면 되는데…

https://help.github.com/articles/generating-an-ssh-key/

이미 예전에 추가를 해놨음에도 불구하고 왜 맥 터미널에서 자꾸 passphrase를 입력하라구 물어보는거야!!

와중에 이전에 입력해둔 문구를 까먹었는데!!!

별다른 방법이 없다. 다시 만들고 등록해두는 방법 밖에는.

다 하고 다음 명령어로 넣어두면 이후에는 안물어볼 것 같다.

ssh-add -K

SonarQube 이용해서 만드는 CI

CI 들어봤나?

Continuous Integration의 약자이다. 해석하자면 “지속적으로 통합하라” 라는 이야기다.  뭘? 코드를.  누가 작성한 코드를?  여러분과 여러분들의 동료들이 작성한 코드를 말한다.

왜 통합을 해야할까?

이유는 간단하다. 통합을 위해서.

뭔 소리냐구?

통합을 지속적으로 시도하는 이유는 바로 필요한 시점에 사고 터지는 걸 막기 위해서다.  누구나 익히 알고 있듯이 모든 개발은 팀 작업이다.  혼자 잘해서 되는 개발은 더 이상은 없다.

사람들이 하나의 목표로 달린다지만 속된말로 꿍꿍이는 다 다르다.  이런 불일치가 지속된다고 가정해보자.  계속 내 일은 내가 알아서 하는 상태로, 너와 나의 일은 깔끔한 문서로 정리되어 있다면?

여러분들은 이들의 작업이 성공적인 결합으로 결론지어질 수 있을거라고 생각하나? 절대 불가능하다.

어떻게해야 할까?

가장 현실적이고 최선인 대답은 가능한 빠른 시간에 합쳐보라는 것이다.  “가능한 빨른 시간”라는 말은 여기서 두 가지 의미를 내포한다.  첫 번째는 현재를 기점으로 가장 이른 시간에 합쳐보는 시도를 해야한다.  두 번째 의미는 이전에 해 본 이후그 다음 번 합칠 때까지 시간을 늦추지 말라는 것이다.  나뉘어져 개발되는 결과를 합쳐보는 시도는 당연히 빨리 해야한다. 아마도 모두가 동의하는 사실일 것이다.

두번째 그럼 얼마나 빨리?  CI라는 단어를 처음 이야기한 Grady Booch님은 종종(?)을 이야기했다. 하지만 최근 CI를 열창(!)하는 에자일이나 XP쪽에서는 가능한 많이 하루를 넘기면 죄악(?)이라고 이야기한다.  개인적으로 동감하는 바이다. 하지만 죄악은 일상의 연속이며 실행하기는 참 어렵다. 그래서 회개는 반드시 해야하는 모양이다.

Best Practices

그럼 CI를 실행하는 최선의 방법은 뭘까?  위키에서 언급된 부분을 번역 수준에서 읊어보자면…

  • 코드 저장소(Code Repostiory)
  • 자동화된 빌드
  • 자동화된 테스팅
  • 일관된 커밋 규칙을 갖기 – 이건 전체 참여자들이 다들 지키는 규칙을 만드는게 어려운 것 같다.
  • 동작 가능한 커밋하기
  • 빨랑 빌드되기
  • Stage 환경 갖추기
  • Nightly build 환경 갖추기
  • 개방된 CI 결과 – 쪽팔린다고 테스트 결과를 숨기거나 빌드 오류를 숨기지 않는다.
  • 자동화된 배포

이걸 종합해보면 아래와 같은 순환적 코드 관리가 이뤄지면 된다.

개발자들은 개발된 결과를 코드 저장소에 저장하고, 저장된 결과는 자동화된 빌드 및 테스트를 통해 결과가 올바른지 확인하고 그 피드백을 통해 신규 코드를 추가하거나 기존 코드의 개선을 진행한다.

적어도 통합은 여러분이 일하는 그 날의 마지막 작업이어야 한다.  가능하면 그 결론이 성공적인 통합인 것으로!

And Then…

그런데 CI의 개념은 알겠지만 이걸 어떻게 만들어야 하는거지?  지금까지 이야기를 정리하면 CI를 구현적 관점에서 만들려면 다음 두 가지 기술이 필요하다.

  • 주기적으로 뭔가를 돌려주는 배치 시스템 – 이거에 딱인 시스템을 우리는 잘 알고 있다. 바로 젠킨스(Jenkins)!
  • 소스 코드의 “분석/빌드/테스트” 결과를 수집해서 리포팅 해주는 물건 – 이 물건이 바로 소나큐브다.

이제 본격적으로 알아보도록 하자.

 

SonarQube

네이버에서는 클로버(Clover)라는 툴을 사용했다. Atlanasian에서 만든 툴인데 기억에 상당히 비쌌던 것 같다. 찾아봤더니. 1년에 이정도면 회사에서 사용하기에 그리 비싼 도구는 아닌 것 같긴한데? 하지만 역시나 빈한한 개발자에게는 무리가 되는 금액이긴 하다.

clover-price

소나큐브는 오픈소스다. 다른 말로 하면 공짜다.  친절한 유지보수는 없지만 덕을 볼려는 개발자들에게 이런 정도의 수고는 의무가 아닐까?

What is it?

근데 정확하게 소나큐브(SonarQube)가 뭐하는 거죠?

앞에서 이야기한 것처럼 소나큐브의 기본 역할은 정적 분석이고 여러분이 작성한 테스트와 꿍짝해서 코드가 테스트에 의해 얼마나 검증됐는지 커버리지(Coverage)를 측정한다.

  • Complexity
  • Duplications
  • Coding Rules
  • Potential Bugs
  • Test Cases
  • Coverage

 

Installation

젠킨스(Jenkins)를 써야겠지? 맥이나 아마 리눅스에선 다음 명령을 이용하면 젠킨스는 바로 설치할 수 있을 것이다.

sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
sudo rpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.key
sudo yum install jenkins

만약 젠킨스가 깔리 서버에 JDK8이상이 설치되지 않았다면 이것도 설치해야 한다.  신상을 원한다면 그건 알아서 확인하자.

curl -v -j -k -L -H "Cookie: oraclelicense=accept-securebackup-cookie" \
http://download.oracle.com/otn-pub/java/jdk/8u73-b02/jdk-8u73-linux-x64.rpm > jdk-8u73-linux-x64.rpm

sudo rpm -ivh jdk-8u73-linux-x64.rpm

인제 준비 완료. 정말 소나큐브를 깔아보자.  먼저 해야할 일은 최신 버전을 다운로드 받는거다.  http://www.sonarqube.org/downloads/ 에서 얻을 수 있다.  최신 버전을 확인한 다음 다음의 과정을 죽 진행하면 설치가 마무리된다.

wget https://sonarsource.bintray.com/Distribution/sonarqube/sonarqube-5.4.zip
unzip sonarqube-*.zip

# setup for auto execution when reboot
sudo ln -s $SONAR_HOME/bin/linux-x86-64/sonar.sh /usr/bin/sonar
sudo chmod 755 /etc/init.d/sonar
sudo chkconfig --add sonar

# start sonar
sonar start

Connecting to Jenkins

소나큐브 서버까지 설치를 했으면, 다시 젠킨스와의 연결 고리를 만든다. 젠킨스에서 지원하는 훌륭한 플러그인들이 많은데 우리가 할 일은 그것들을 콘솔상에서 깔아주기만 하면 된다.  대부분은 이미 설치되어 있고 윤택한 개발을 위해 다음 플러그인들만 추가로 설치한다. “젠킨스 관리 > 플러그인 관리” 메뉴를 통해 추가로 플러그인을 설치할 수 있다.

  • Git client plugin
  • Git plugin
  • Maven integration plugin
  • SonarQube plugin

자, 여기까지 했으면 다음으로 이것들에 대한 설정을 잡는다. 설정은 젠킨스 전역 설정이 있고, 개별 프로젝트 단위로 해야할 설정이 따로 있다.

전역 설정

설정은 “젠킨스 관리 > 시스템 설정” 메뉴에서 잡는다.  다른 플러그인은 알아서 깔면 될 것 같고,  소나큐브에 대한 설정은 아래 화면을 참고한다.

sonarqube-jenkins-config

잡아줘야 할 항목들 가운데 대부분은 기본 값으로 설정한다.  만약 본인이 이전 소나큐브 설치 과정에서 변경을 했다면 해당 값을 여기에 입력한다.  예를 들어 기본으로 제공해주는 embeded DB가 아닌 MySQL DB를 사용하는 경우에는 해당 값을 반영해야겠다.

주의해야할 필드는 “Additional analysis properties” 항목 값이다. 다음 2가지 속성에 대한 값이 반영되야한다.  각 속성에 여러 값을 입력하는 경우는 콤마(,)로 구분하면 되고, 이 두개 항목의 구분은 세미콜론(;)으로 구분한다.

  • sonar.sources
  • sonar.java.binaries

만약 다른 프로그래밍 언어 혹은 개발 플랫폼을 이용한다면 그 값을 프로젝트별 설정에서 추가로 설정해줘야 한다.

프로젝트별 설정

우리는 자바 개발을 좋아하는 개발자이기 때문에 자바, 메이븐(Maven) 기준이다.

메이븐에서 커버리지를 측정할려면 각 코드가 어떻게 수행되는지를 측정해야 한다.  소나큐브와 가장 많이 어울리는 툴이 Jacoco라는 툴이다.  물론 이 측정이 일반 메이븐 필드 과정에 껴들면 곤란하기 때문에 일반 빌드와는 별도로 설정하도록 되어 있으며 이를 위한 설정은 아래 plugin 설정으로 묶어서 build  과정에 포함시켜둔다.

      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>0.7.6.201602180812</version>
        <executions>
          <execution>
            <id>default-prepare-agent</id>
            <goals>
              <goal>prepare-agent</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

pom.xml 파일에 이렇게 추가하고, 이제 젠킨스 프로젝트 설정을 보자.  플러그인이 돌아야지  커버리지를 볼 수 있기 때문에 빌드 Goal을 아래와 같이 설정한다.

sonarqube-jenkins-proj-config

  • Goals and options: clean org.jacoco:jacoco-maven-plugin:prepare-agent install
  • Analysis properties
    • sonar.projectKey
    • sonar.projectName
    • sonar.projectVersion

여기에서 설정하는 project 필드는 소나큐브의 프로젝트를 정의한다.  여기에서 이름이나 버전등을 변경하면 새로운 프로젝트 소나큐브에 만들어진다. 만약 전역 관점에서 sources, binaries 들을 설정하지 않았다면 여기에서 추가로 설정할 수 있다.

설정을 마치고 메이븐 프로젝트를 빌드해보자.  그럼 보게 될 것이다!!

sonarqube-installed

 

Ember.js – Summary of core concept and usage

현재 개발하고 있는 Single Page WebApp의 기반 프레임워크로 Ember.js를 사용중이다. 한번 작업을 할 때는 대강 구조를 알겠다 싶었지만 몇년을 손에 익힌 언어와 프레임웍이 아니다보니 자꾸 헷갈리게 된다.  다시 한번 머리속의 내용을 정리하는 차원에서 기록해둔다.

대부분의 핵심 내용은 Ember.js 사이트에서 자료를 구했다.  여기에서는 업무를 진행하기 위해 알고 있어야 하는 부분들만 추려봤다. 좀 더 상세한 자료는 아래 사이트에서 찾아보면 상세한 자료를 얻을 수 있다.

Ember.js: https://guides.emberjs.com/v2.4.0/getting-started/core-concepts/

Ember.js Workflow

Ember의 간단한 흐름은 아래와 같다.  시나리오는 사용자가 http://localhost:4200/rentals 라는 URL로 접근하는 경우이다.

  • 라우터로 “rentals”를 등록한다.  이때 묵시적으로 “/” 라우터도 등록된다.
  • /rentals URI로 접근을 위한 로직 처리를 Ember.Router 객체가 제공한는 Method를 Override하는 방식으로 처리한다. 여기에서는 템플릿에서 참조하는 데이터 모델을 제공하기 위해 model() 메소드를 Overriding했다.
  • 실제 UI를 HBS(Handlebars)를 이용해서 그린다.
  • 세부 영역의 UI 핸들링을 위해서 Component를 활용한다.

이제 각각에 대한 세부적인 부분들을 살펴보도록 하자.

App Directory Structure

Ember를 사용하는 앱 관점에서 다음과 같은 디렉토리 구조를 가지고 있다.

ember-directory

  • src/hbs – 라우터 정의에 따른 HBS 파일 경로. 파일명은 라우터의 이름과 동일해야 함.
  • src/js – Ember의 주 실행 코드 영역
    • adapter
    • components
    • controller
    • helpers
    • models
    • routers
    • app.js

이제 세부적인 각 항목의 역할을 아는 만큼 정리해본다.

Core concepts

Router

URI의 각 Path 항목들에 Mapping된 Route를 정의한다.  예를 들어 다음과 같은 URI를 살펴보자.

/family/father/job

이 부분은 다음과 같은 영역으로 구분된다.

  1. / – FamilyIndexRoute
  2. /family – FamilyRoute
  3. /family/ – FatherIndexRoute
  4. /family/father – FatherRoute

각 IndexRoute는 명시적으로 선언하지 않으면 암묵적으로 선언되며 또한 실행된다.  위 구조를 적절히 라우터로 선언하면 아래와 같은 JS 코드로 표현할 수 있다.

App.Router.map(function() {
  this.resource.route(index, { path: '/' });
  this.resource.route('family', { path: '/family' }, function() {
    this.resource.route('father');
});

평범한 거 말고 아래처럼 와일드 카드 정의도 가능하다.

Router.map(function() {
  this.route('catchall', { path:'/*wildcard' });
});

Template

Handlebars(hbs) 템플릿 언어로 기술되고 UI를 그린다.  그릴 때 필요한 데이터는 Model  혹은 Controller를 통해 정의된 속성으로 얻을 수 있다.  HBS 자체도 제어 구조를 제공하며, 이를 활용하면 간단한 반복문이나 예외 케이스등을 처리할 수 있다.

기본적으로는 HTML을 거의 동일하게 사용할 수 있으며, HBS 형식을 이용해 모델 혹은 속성을 참조할 수 있따.

  • Expression – {{firstname}} – 모델의 필드 이름을 적는다.
  • Outlets – {{outlet}} – 다른 템플릿(hbs) 파일의 내용을 outlet 이라는 공간에 들어와서 재활용 가능하도록 한다.  아래의 Index Routing에서 만약 별도의 hbs  파일을 두지 않는다면 outlet이 기본 hbs로 제공된다.

HBS 파일은 별도의 확장자를 가지기 때문에 브라우저를 통해 열어볼 수 없다.  이 부분은 디자인팀과의 협업 관점에서 마이너스 요인이 된다.

Component

Component는 HBS와 맞물려서 UI를 그릴 때 사용한다.

Model

Persistent data model, HBS에서 모델을 이용해서 화면에 내용을 출력한다.  Ember에서 손쉽게 Async ajax 호출을 위한 방안을 제공한다.

  • @each – Object 배열의 특정 element 하나를 지칭하고 싶을 때 사용한다.
  • Ember.observer – 객체내의 특정 변수의 값이 변경된 경우, 이에 대한 비동기 작업을 수행한다.

모델은 Adapter와 연계해서 Ember를 통해 동적 Ajax Request를 보낼 수 있도록 지원한다.

Controller

HBS에서 사용할 속성을 정의한다.  속성은 데이터가 될 수도 있고, 함수가 될 수도 있다.

Route

Route는 URL 접근에 따라 Controller, HBS 실행을 전/후에 실행되어야 할 JS action을 정의한다.  실행은 Route 객체의 이벤트 메소드를 Overriding하여 이뤄진다. 각 메소드의 구현체에서 model 데이터를 얻기 위해 어떤 일을 해야하는지 혹은 다른 URI 위치로 이동해야한다면 이동을 한다든지의 동작을 수행한다.

App.TicketsIndexRoute = Ember.Route.extend({
  model: function(params) {
    var slug = 'tickets';
    var activeTab = params.tab_id || null;
    var blockPromise = this.store.query('slug', {contentType: slug}).then(function(list) {
      return App.DefaultLocaleHelper.getFirstMatchingLocale(list);
    });

    return Ember.RSVP.hash({
      block: blockPromise,
      activeTab: activeTab
    });
  },
  afterRender: function() {
    $(document).foundation();
  }
});

The Application

Ember 앱이 만들어지게 되면 다음과 같이 기본 세가지 파일이 만들어진다.

{{appName}} - {{model.title}}

HBS에서의 변수 참조는 아래와 같이 처리된다.

  • Controller 속성(변수/함수) – 정의된 이름을 직접 참조
  • Model 속성 – “model.속성명”

Controller는 모델 이외에 추가로 정의된 속성을 정의한다.

 
App.ApplicationController = Ember.Controller.extend({
  appName: 'My First Example' 
}); 

라우트에서는 Model을 요청하는 메소드를 후킹해서 title 이라는 신규 속성이 내려가도록 한다.

App.ApplicationRoute = Ember.Route.extend({
  model() {
    return { title: "Hello World" };
  }
});

만약 다른 동작이 실행되길 원한다면 아래와 같이 화면 렌더링이 되기 전에 다른 곳으로 이동되도록 처리할 수도 있다.

App.LeagueIndexRoute = Ember.Route.extend({
  beforeModel: function(transition) {
    var league = this.modelFor('league');
    this.transitionTo('tournament', league.get('mainTournament.title'));
  }
});

 

Dynamic Segments – Path Variable

Path Variable을 사용해야 하는 경우에 이를 처리 하는 방법은 아래와 같다.

var Router = Ember.Router.extend();

Router.map(function(){
  this.route('post', { path: '/posts/:post_id' });
});

위와 같이 :post_id 와 같은 Path Variable을 입력으로 받아들인다면 해당 값은 아래와 같이 참조가 가능하다.

import ajax from 'ic-ajax';

export default Ember.Route.extend({
  model(params) {
    return ajax('/my-service/posts/' + params.post_id);
  }
});

 

Index Routing

만약 /favorite 같이 URI를 잡으면 Ember 내부적으로는 / 에 대한 라우팅과 /favorite에 대한 라우팅 두 개가 모두 선언된 것 같과 같다.  따라서 다음 두개의 코드는 서로 같은 의미를 가진다.

Router.map(function(){
  this.route('favorites');
});
Router.map(function(){
  this.route('index', { path: '/' });
  this.route('favorites');
});

재미있는건 /favorite으로 이동하라는 요청을 받았을 때 Ember가 내부적으로 동작은 아래의 순서를 따른다.

  • 먼저 / 에 대응하는 model, controller, hbs 를 실행한다.
  • 만약 인덱스에 대응하는 hbs가 없으면 outlet으로만 채워진 hbs가 기본으로 제공된다.
  • 그 다음에 /favorite에 대응하는 model, controller, hbs가 실행된다.

 

Advanced Features

Route class member methods for overriding

  • model()
  • setupController(controller) – controller.set(‘model’, model)를 사용해서 Controller 내부에서 참조할 값들을 셋팅할 수 있다.
  • setupController(controller, model) – this.controllerFor(‘otherController’)를 사용해서 다른 Controller의 초기화도 가능하다.
  • renderTemplate() – this.render(‘template_name’)을 활용하거나 확장해서 다른 Controller를 지정하거나 다중 Outlet을 사용할 수 있다.
  • beforeModel()
  • afterModel(posts, transition)
  • activate()

 

Component

To be summarized

Adapter

Ember에서 Adapter는 Model과 꿍짝을 이뤄 API 서버에서 데이터를 받아와 이를 Model Object 만드는 역할을 수행한다.   Ember 환경에서 API 시스템을 통한 데이터 처리는 RESTful API 규격을 따라 GET/POST/PUT/DELETE 처리에 의해 데이터의 가공이 이뤄진다.  조회 요청을 위한 파라미터의 처리는 API의 Path Variable을 통해 처리되는 것을 기본으로 한다.

 

어플리케이션 전체 수준에서 동일한 API를 사용하는 경우에는 src/js/adapter/application.js 파일에 이를 정의함으로써 처리할 수 있다.

export default DS.RESTAdapter.extend({
  namespace: 'api/1',
  host: 'https://api.example.com'
});
  • namespace – URI의 prefix에 해당 문자열을 추가한다.
  • host – API Host를 지정한다.

만약 개별 모델에 따라 이를 정의하고자 한다면 각 모델 이름 뒤에 Suffix로 Adapter를 부여한 객체를 별도로 만들면 된다.  예를 들어 League라는 모델의 경우 아래와 같이 LeagueAdapter를 만들어주면 된다.

(function (App, Ember, DS) {
  App.NavItemAdapter = DS.RESTAdapter.extend({
    namespace: 'api/v1',
    host: Riot.services.api
  });
}(App, Ember, DS));

 

Conclusion

결론은 이러하다.

EmberFlowSummary