잊지 않겠습니다.

queryDSL이 4.x대로 변경이 되면서 package명에 큰 변화가 생겼습니다. 기존 com.mysema.querydsl 에서 com.querydsl로 package명이 바뀌어서 간략화 되었지요.

기존 querydsl 처리 부분에 대한 build.gradle을 변경한다면 다음과 같습니다.


dependencies {
compile("org.springframework.boot:spring-boot-starter-web") {
exclude module: "spring-boot-starter-tomcat"
}
compile 'com.graphql-java:graphql-java:2.1.0'
compile("org.springframework.boot:spring-boot-starter-jetty")
compile("org.springframework.boot:spring-boot-starter-actuator")
testCompile("junit:junit")
testCompile("org.springframework.boot:spring-boot-starter-test")
compile 'com.querydsl:querydsl-apt:4.1.4'
compile 'com.querydsl:querydsl-jpa:4.1.4'
compile 'org.springframework.data:spring-data-jpa:1.10.4.RELEASE'
compile group: 'org.hibernate', name: 'hibernate-core', version: '5.2.3.Final'
compile "org.hibernate:hibernate-entitymanager:5.2.3.Final"
compile 'com.h2database:h2:1.4.187'
compile group: 'org.aspectj', name: 'aspectjrt', version: '1.8.9'
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.8.9'
compileOnly "org.projectlombok:lombok:1.16.12"
}


sourceSets {
main {
java {
srcDirs 'src/main/java', 'src/main/generated'
}
}
}

task generateQueryDSL(type: JavaCompile, group: 'build', description: 'Generates the QueryDSL query types') {
file(new File(projectDir, "/src/main/generated")).deleteDir()
file(new File(projectDir, "/src/main/generated")).mkdirs()
source = sourceSets.main.java
classpath = configurations.compile + configurations.compileOnly
options.compilerArgs = [
"-proc:only",
"-processor", "com.querydsl.apt.jpa.JPAAnnotationProcessor"
]
destinationDir = file('src/main/generated')
}

compileJava {
dependsOn generateQueryDSL
}

clean.doLast {
file(new File(projectDir, "/src/main/generated")).deleteDir()
}


기존의 generateQueryDSL task를 유지할 필요 없이, java compile option으로 QClass 생성을 넣어주는것으로 script를 간략화시키는것이 가능합니다. 또한 Q class가 존재할 때, 기존 Q Class를 지워주지 않으면 compile 시에 에러를 발생하게 되기 때문에 지워주는 작업역시 doFirst에 같이 실행하고 있습니다.

기존의 build.gradle보다 간략해보여서 전 개인적으로 맘에 드네요.

Posted by Y2K
,

Dependency Injection

angularjs2 2016. 9. 28. 15:17

Dependency Injection은 angular, angular2의 핵심 기능입니다.

이미 Server side 부분에서 자주 언급되고 있는 것으로, 특정 객체가 사용하는 객체군들을 만들어서 객체를 사용하는 쪽에서 골라 사용하는 개념이지요.

angularjs2는 자체 Dependency Injection Framework를 가지고 있습니다. 모든 ComponentServicePipe,Delegate는 Dependency Injection Framework를 통해 사용되어야지 됩니다.

Root Dependency Injection Tree

angularjs2 applicaiton은 한개 이상의 NgModule을 갖습니다. bootstrap에 연결된 이 NgModule에서 선언된providers import의 경우에는 전체 Global Root Dependency Injection Tree에 등록되게 됩니다.

NgModule에 등록된 providers와 import는 모든 Component에서 사용가능합니다. 각 Component에서 한정적으로만 사용될 Service나 PipeDelegate의 경우에는 각 Component별 provider에 등록해서 사용하면 됩니다.

@Injectable

@Injectable annotation은 DI 대상을 지정합니다. service에서만 @Injectable annotation이 사용되게 됩니다. 다른@Component@Pipe@Directive annotation 모두 @Injectable의 subset이기 때문에 따로 지정할 필요는 없습니다.

Inject Provider

기본적으로 providers에는 그 객체가 지정됩니다. 다음 코드는 일반적인 providers code입니다.

providers: [ Logger ]

이는 축약된 표현으로 원 표현은 다음과 같습니다.

providers: [ { provide: Logger, useClass: Logger } ]

Logger라는 객체를 얻어낼려고 하면 Logger를 넘겨준다는 표현식이 됩니다. 이를 좀 응용하면 다른 일들을 할 수 있습니다. Logger로 지정해두고, AnotherLogger를 inject 시킬 수 있다는 뜻이 됩니다. 다음과 같이요.

providers: [ AnotherLogger, { provide: Logger, useClass: AnotherLogger } ]

위 표현식은 Logger와 AnotherLogger 2개의 instance가 생성되게 됩니다. 모든 Logger를 AnotherLogger로 바꾸어 사용하고 싶은 경우에는 다음과 같이 선언합니다.

providers: [ AnotherLogger, { provide: Logger, useExisting: AnotherLogger } ]

지정된 객체는 singleton instance로 동작하는 것이 기본값입니다. 사용될 때마다 새로운 객체를 얻어내기 위해서는Factory를 생성해야지 됩니다. Factory는 function 형태로 다음과 같이 선언될 수 있습니다.

const anotherLoggerFactory = (logger: Logger) => { return new AnotherLogger(logger); };

이렇게 선언된 Factory를 다음과 같이 providers에 등록하면 이제 AnotherLogger는 사용될 때마다 신규 객체를 생성해서 처리가 되게 됩니다.

providers : [ { provide: AnotherLogger, useFactory: anotherLoggerFactory, deps: [ Logger ] } ]

Value Provider

직접적인 을 제공하고 싶을 때 사용할 수 있습니다. applicaiton의 여러 설정들이나 상수값들을 지정할 때 사용 가능합니다. 다음과 같은 값을 전달할 때 사용할 수 있습니다.

const appConfig = { apiUrl: 'http://testserver:8080/api', userToken: 'jkfla;sjdifpqiowjerkjskadj;fla' };

그런데, 이와 같은 값을 어떻게 하면 provider에 지정이 가능할지 의문이 듭니다. 위 은 객체가 아닙니다. 객체가 아니기 때문에 DI에서 이용되는 Class가 무엇인지 알 수 없습니다. 그래서 angular2에서는 OpaqueToken이라는 것을 제공하고 있습니다. OpaqueToken을 이용해서 다음과 같이 선언후, @Inject에서 OpaqueToken을 지정해주면 됩니다.

import { OpaqueToken } from '@angular/core'; export const APP_CONFIG = new OpaqueToken('app.config'); .... import { APP_CONFIG } from './config'; providers: [ { provide: APP_CONFIG, useValue: appConfig } ] .... constructor(@Inject(APP_CONFIG) config: any) { console.log(config.apiUrl); }

Manual Injector

직접적으로 객체를 DI tree에서 가지고 와서 처리하고 싶을 때도 있을 수 있습니다. sping에서 ApplicationContext에서 바로 객체를 들고오고 싶을 그럴 때와 같이요. 이러한 경우에는 다음과 같이 처리하면 됩니다.

export class InjectorComponent { car: Car = this.injector.get(Car); heroService: HeroService = this.injector.get(HeroService); hero: Hero = this.heroService.getHeroes()[0]; constructor(private injector: Injector) { } }

Root Provider에 이미 Injector 서비스가 등록이 되어 있습니다. 이 등록된 서비스를 가지고 와서 사용하면 됩니다. 개인적으로는 정말 추천하지 않는 방법이긴 합니다.;

SUMMARY

angular2는 다양한 방법의 DI를 제공하고 있습니다. Spring이나 CastleNinject등을 사용해보신 분들이라면 매우 익숙하겠지만, 지금까지 FrontEnd만 해본 사람들에게는 복잡한 내용임은 사실입니다. 그렇지만, DI는 angular2에서 가장 중요한 기능중 하나입니다. 객체를 어떻게 다루고, 관리할지에 대한 pattern을 결정하는 설정이니까요. 개인적으로는OpaqueToken이 매우 인상적인것 같습니다.

Posted by Y2K
,

Data Binding

angularjs2 2016. 9. 23. 00:26

angular2는 angular1과 다르게 한개의 Page가 여러개의 Component로 이루어집니다. 그리고 그 Component간의 데이터 전달은 Input/Output을 통해서 이루어집니다. 예전 ng-model이 이제 단방향으로 나뉘어져있다고 생각하면 쉽습니다. (ngModel과 같이 양방향 역시 존재합니다.)

기존 angular1에서의 directive에 데이터를 넣을때를 생각해보시면 쉽습니다. directive를 하나 선언하게 되면 그에 대한 scope의 범위를 다음과 같이 선언하게 됩니다.

scope: { ngModel: '=' }

위와 같이 선언된 ngModel은 html상에서 다음과 같이 사용되어질 수 있습니다.

<app-directive ng-model="models.data"></app-directive>

angular2에서는 Input과 Output을 명확하게 구분합니다. 이 부분이 어찌보면 angular1과 angular2간의 가장 큰 차이라고도 볼 수 있습니다.

기존 양방향 binding의 경우 performance의 문제가 발생했었고, 이는 angular1에서의 성능향상의 가장 큰 걸림돌이 되었던 dirty-watch의 문제점이기도 했습니다. 이 부분은 react에서도 적용되어 있는것으로, javascript에서의 refrencetype의 참조가 아닌, value로서 참조를 하게 됩니다. 이 부분은 매우 중요합니다. 결국 angular1에서의 양방향 reference binding의 문제로 binding된 model은 2개의 copy 본이 만들어지고 dirty-watch 과정을 통해 copy본과의 차이점을 알아내고 그 값을 다시 view에 binding하는 과정에서 angular1은 많은 시스템 자원을 소비하고 있었지만, angular2의 경우에는 이 과정이 없어지는거지요. binding자체가 값으로 set되는것이고, 그 set된 값은 변경되기 전에는 rendering과정을 거치지 않게 됩니다.

서론이 길었습니다. 이제 angular2에서의 DataBinding을 알아보도록 하겠습니다.

Data Binding의 종류

  • interpolation : {{}}로 표현되는 binding입니다. 값을 화면에 표시하는 용도로 사용됩니다.
  • property binding: []로 표현되는 binding입니다. Component의 property값을 set할때 사용됩니다.
  • event binding: ()로 표현됩니다. Component에서 발생되는 event를 get할때 사용됩니다.
  • two-way data binding: [()]로 표현됩니다. Component에서 값을 getset을 할 수 있습니다. 이는 ngModel directive를 이용해서만 사용 가능합니다.

먼저 간단한 2개의 Component를 통해, interpolationproperty bindingevent binding을 알아보도록 하겠습니다.

import { Component, OnInit } from '@angular/core'; import { TenantService } from '../shared/services'; @Component({ selector: 'app-feature', templateUrl: './feature.component.html', styleUrls: ['./feature.component.css'] }) export class FeatureComponent implements OnInit { tenantList: any[]; constructor(private tenantService: TenantService) { } ngOnInit() { this.tenantService.listAll().then(tenants => { this.tenantList = tenants; }); } selectedTenant(event) { console.log(event); } }
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import * as _ from 'lodash'; @Component({ selector: 'app-tenant-list', templateUrl: './tenant-list.component.html', styleUrls: ['./tenant-list.component.css'] }) export class TenantListComponent implements OnInit { @Input() tenants: any[]; @Output() select = new EventEmitter(); constructor() { } ngOnInit() { } selectTenant(tenantId: number) { const selectedTenant = _(this.tenants).find(tenant => tenant.id === tenantId); this.select.emit(selectedTenant); }; }

FeatureComponent와 TenantListComponent가 있습니다. FeatureComponent안에서 TenantListComponent가 Table로 구현되는 아주 간단한 구조입니다.

이에 따른 html은 각각 다음과 같습니다.

// feature.component.html <app-tenant-list [tenants]="tenantList" (select)="selectedTenant($event)"></app-tenant-list>
// tenant-list.component.html <table class="table table-hover"> <thead> <th>Id</th> <th>Name</th> </thead> <tbody> <tr *ngFor="let tenant of tenants" (click)="selectTenant(tenant.id)"> <td>{{tenant.id}}</td> <td>{{tenant.defaultInfo.name}}</td> </tr> </tbody> </table>

먼저 feature.component.html에서는 property binding과 event binding이 보여집니다. TenantListComponent에서 정의된 Property인 tenants가 [tenants]="tenantList"로 set을 하고 있는 것을 보실 수 있습니다.

event binding의 경우에는 다양합니다. 우리가 주로 알고 있는 web에서의 click과 같은 event들이 event binding으로 처리됩니다. 이는 tenant-list.component.html을 보면 확인이 가능합니다.

<tr *ngFor="let tenant of tenants" (click)="selectTenant(tenant.id)">

click event가 발생하게 되면 selectTenant method가 호출이 되게 됩니다. selectTenant method는 안에서 EventEmitter 객체를 통해 이벤트를 발생시킵니다. Component에서 외부로 노출되는 값은 모두 Event가 되게됩니다.

Summary

angular2는 4개의 binding type을 가지고 있습니다. interpolationevent bindingproperty bindingtwo-way binding이 있습니다. 4개의 용도는 다음과 같습니다.

  • interpolation: model의 값을 단순 display 할때 사용됩니다.
  • event bindingParent Component에서 Child Component의 값을 가지고 올때(get) 사용됩니다. - @Output과 같이 사용됩니다.
  • property bindingParent Component에서 Child Component의 값을 설정할 때(set) 사용됩니다. - @Input과 같이 사용됩니다.
  • two-way bindingComponent안에서 value와 rendering결과를 일치시킬 때, 사용됩니다. - input element와 같이 사용되는 경우가 많습니다.

Binding 부분은 angular2의 가장 핵심적인 부분입니다. 직접 예시 application을 만들어서 해보는것이 좋을 것 같습니다. 그럼 Happy Coding!

Posted by Y2K
,