블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 


글은 제가 텐서플로우와 딥러닝을 공부하면서 블로그에 메모해놨던 내용을 모아놓은 글입니다.

혼자 공부하면서 어려웠던 점도 있었기 때문에, 저처럼 텐서플로우와 딥러닝을 공부하시는 분들께 도움이 되고자 자료를 공개합니다.

텐서플로우 초기버전부터 작성하였기 때문에, 다소 코드가 안맞는 부분이 있을 있으니 양해 부탁드리며, 글은 개인이 스터디용으로 자유롭게 사용하실 있으며, 단체나 기타 상용 목적으로 사용은 금지 됩니다.


머신러닝 이북-수포자를 위한 머신러닝.pdf.zip


혹시 이 교재로 공부하시다가 잘못된 부분을 수정하셨으면 다른분들을 위해서 친절하게 댓글을 달아주시면 감사하겠습니다.


그리고 오프라인 스터디 그룹을 진행하시는 분들을 위해서 지원을 해드립니다.

  • 발표용 프리젠테이션 파일
  • 실습 자료
  • 온라인 실습용 https://google.qwiklabs.com/catalog 토큰
스터디 지원을 위해서는 
1. https://www.facebook.com/groups/googlecloudkorea/ 구글 클라우드 사용자 그룹에 가입 하신후
2. https://www.meetup.com/GDG-Cloud-Korea 에 가입하신 후에, 스터디 모임을 매주 진행하실때 마다 밋업을 여시면 됩니다.
그후에, 저한테 페이스북으로 연락 주시면 https://www.facebook.com/terry.cho.7 제가 자료와 함께 실습 토큰 (무료 크레딧)을 제공해 드립니다.


저작자 표시 비영리
신고

 

얼굴 인식 모델을 만들어보자

#3 - 학습된 모델로 예측하기


조대협 (http://bcho.tistory.com)


앞글에 걸쳐서 얼굴 인식을 위한 데이타를 수집 및 정재하고, 이를 기반으로 얼굴 인식 모델을 학습 시켰다.

 

 

이번글에서는 학습이 된 데이타를 가지고, 사진을 넣어서 실제로 인식하는 코드를 만들어보자

전체 소스 코드는 https://github.com/bwcho75/facerecognition/blob/master/2.%2BFace%2BRecognition%2BPrediction%2BTest.ipynb 와 같다.

모델 로딩 하기

 

모델 학습에 사용한 CNN 모델을 똑같이 정의한다. conv1(),conv2(),conv3(),conv4(),fc1(),fc2(), build_model() 등 학습에 사용된 CNN 네트워크를 똑같이 정의하면 된다.

 

다음으로 이 모델에 학습된 값들을 채워 넣어야 한다.

# build graph

images = tf.placeholder(tf.float32,[None,FLAGS.image_size,FLAGS.image_size,FLAGS.image_color])

keep_prob = tf.placeholder(tf.float32) # dropout ratio

 

예측에 사용할 image 를 넘길 인자를  images라는 플레이스홀더로 정의하고, dropout 비율을 정하는 keep_prob도 플레이스 홀더로 정의한다.

 

prediction = tf.nn.softmax(build_model(images,keep_prob))

 

그래프를 만드는데, build_model에 의해서 나온 예측 결과에 softmax 함수를 적용한다. 학습시에는 softmax 함수의 비용이 크기 때문에 적용하지 않았지만, 예측에서는 결과를 쉽게 알아보기 위해서  softmax 함수를 적용한다. Softmax 함수는 카테고리 별로 확률을 보여줄때 전체 값을 1.0으로 해서 보여주는것인데, 만약에 Jolie,Sulyun,Victora 3개의 카테코리가 있을때 각각의 확률이 70%,20%,10%이면 Softmax를 적용한 결과는 [0.7,0.2,0.1] 식으로 출력해준다.

 

sess = tf.InteractiveSession()

sess.run(tf.global_variables_initializer())

 

다음 텐서플로우 세션을 초기화 하고,

 

saver = tf.train.Saver()

saver.restore(sess, 'face_recog')

 

마지막으로 Saver의 restore 함수를 이용하여 ‘face_recog’라는 이름으로 저장된 학습 결과를 리스토어 한다. (앞의 예제에서, 학습이 완료된 모델을 ‘face_recog’라는 이름으로 저장하였다.)

 

예측하기

로딩 된 모델을 가지고 예측을 하는 방법은 다음과 같다. 이미지 파일을 읽은 후에, 구글 클라우드 VISION API를 이용하여, 얼굴의 위치를 추출한후, 얼굴 이미지만 크롭핑을 한후에, 크롭된 이미지를 텐서플로우 데이타형으로 바꾼후에, 앞서 로딩한 모델에 입력하여 예측된 결과를 받게 된다.

 

얼굴 영역 추출하기

먼저 vision API로 얼굴 영역을 추출하는 부분이다. 앞의 이미지 전처리에 사용된 부분과 다르지 않다.

 

import google.auth

import io

import os

from oauth2client.client import GoogleCredentials

from google.cloud import vision

from PIL import Image

from PIL import ImageDraw

 

FLAGS.image_size = 96

 

# set service account file into OS environment value

os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/Users/terrycho/keys/terrycho-ml.json"

 

위와 같이 구글 클라우드 Vision API를 사용하기 위해서 억세스 토큰을 Service Account 파일로 다운 받아서 위와 같이 GOOGLE_APPLICATION_CREDENTIALS 환경 변수에 세팅 하였다.

 

visionClient = vision.Client()

print ('[INFO] processing %s'%(imagefile))

 

#detect face

image = visionClient.image(filename=imagefile)

faces = image.detect_faces()

face = faces[0]

 

다음 vision API 클라이언트를 생성한 후에, detect_faces() 를 이용하여 얼굴 정보를 추출해낸다.

 

print 'number of faces ',len(faces)

 

#get face location in the photo

left = face.fd_bounds.vertices[0].x_coordinate

top = face.fd_bounds.vertices[0].y_coordinate

right = face.fd_bounds.vertices[2].x_coordinate

bottom = face.fd_bounds.vertices[2].y_coordinate

rect = [left,top,right,bottom]

 

추출된 얼굴 정보에서 첫번째 얼굴의 위치 (상하좌우) 좌표를 리턴 받는다.

얼굴 영역을 크롭하기

앞에서 입력 받은 상하좌우 좌표를 이용하여, 이미지 파일을 열고,  크롭한다.

 

fd = io.open(imagefile,'rb')

image = Image.open(fd)

 

import matplotlib.pyplot as plt

# display original image

print "Original image"

plt.imshow(image)

plt.show()

 

 

# draw green box for face in the original image

print "Detect face boundary box "

draw = ImageDraw.Draw(image)

draw.rectangle(rect,fill=None,outline="green")

 

plt.imshow(image)

plt.show()

 

crop = image.crop(rect)

im = crop.resize((FLAGS.image_size,FLAGS.image_size),Image.ANTIALIAS)

plt.show()

im.save('cropped'+imagefile)

 

크롭된 이미지를 텐서플로우에서 읽는다.

 

print "Cropped image"

tfimage = tf.image.decode_jpeg(tf.read_file('cropped'+imagefile),channels=3)

tfimage_value = tfimage.eval()

 

크롭된 파일을 decode_jpeg() 메서드로 읽은 후에, 값을 tfimage.eval()로 읽어드린다.

 

tfimages = []

tfimages.append(tfimage_value)

 

앞에서 정의된 모델이 한개의 이미지를 인식하는게 아니라 여러개의 이미지 파일을 동시에 읽도록 되어 있기 때문에, tfimages라는 리스트를 만든 후, 인식할 이미지를 붙여서 전달한다.

 

plt.imshow(tfimage_value)

plt.show()

fd.close()

 

p_val = sess.run(prediction,feed_dict={images:tfimages,keep_prob:1.0})

name_labels = ['Jessica Alba','Angelina Jolie','Nicole Kidman','Sulhyun','Victoria Beckam']

i = 0

for p in p_val[0]:

   print('%s %f'% (name_labels[i],float(p)) )

   i = i + 1

 

tfimages 에 이미지를 넣어서 모델에 넣고 prediction 값을 리턴 받는다. dropout은 사용하지 않기 때문에, keep_prob을 1.0으로 한다.

나온 결과를 가지고 Jessica, Jolie,Nicole Kidman, Sulhyun, Victoria Beckam 일 확률을 각각 출력한다.


전체 코드는 https://github.com/bwcho75/facerecognition/blob/master/2.%2BFace%2BRecognition%2BPrediction%2BTest.ipynb


다음은 설현 사진을 가지고 예측을 한 결과 이다.


 

이 코드는 학습된 모델을 기반으로 얼굴을 인식이 가능하기는 하지만 실제 운영 환경에 적용하기에는 부족하다. 파이썬 모델 코드를 그대로 옮겼기 때문에, 성능도 상대적으로 떨어지고, 실제 운영에서는 모델을 업그레이드 배포 할 수 있고, 여러 서버를 이용하여 스케일링도 지원해야 한다.

그래서 텐서플로우에서는 Tensorflow Serving 이라는 예측 서비스 엔진을 제공하고 구글 클라우에서는 Tensorflow Serving의 매니지드 서비스인, CloudML 서비스를 제공한다.

 

앞의 두 글이 로컬 환경에서 학습과 예측을 진행했는데, 다음 글에서는 상용 서비스에 올릴 수 있는 수준으로 학습과 예측을 할 수 있는 방법에 대해서 알아보도록 하겠다.

 

저작자 표시 비영리
신고

파이어베이스 애널러틱스를 이용한 모바일 데이타 분석 #1-Hello Firebase

조대협 (http://bcho.tistory.com)


얼마전에 구글은 모바일 백앤드 플랫폼인 파이어베이스를 인수하고 이를 서비스로 공개하였다.

파이어 베이스는 모바일 백앤드의 종합 솔루션으로, 크래쉬 리포팅, 리모트 컨피그를 이용한 A/B 테스팅 플랫폼, 클라우드와 자동 동기화가 가능한 리얼타임 데이타 베이스, 사용자 인증 기능, 강력한 푸쉬 플랫폼 다양한 모바일 기기에 대해서 테스트를 해볼 수 있는 테스트랩 등, 모바일 앱 개발에 필요한 모든 서비스를 제공해주는 종합 패키지와 같은 플랫폼이라고 보면 된다. 안드로이드 뿐만 아니라 iOS까지 지원하여 모든 모바일 앱 개발에 공통적으로 사용할 수 있다.



그중에서 파이어베이스 애널러틱스 (Firebase analytics)는 모바일 부분은 모바일 앱에 대한 모든 이벤트를 수집 및 분석하여 자동으로 대쉬 보드를 통하여 분석을 가능하게 해준다.


이 글에서는 파이어베이스 전체 제품군중에서 파이어베이스 애널러틱스에 대해서 수회에 걸쳐서 설명을 하고자 한다.


파이어베이스 애널러틱스

이미 시장에는 모바일 앱에 대한 데이타 분석이 가능한 유료 또는 무료 제품이 많다.

대표적으로 야후의 flurry, 트위터 fabric, 구글 애널러틱스등이 대표적인 제품군인데, 그렇다면 파이어베이스가 애널러틱스가 가지고 있는 장단점은 무엇인가?


퍼널 분석 및 코호트 분석 지원

파이어베이스 애널러틱스는 데이타 분석 방법중에 퍼넬 분석과 코호트 분석을 지원한다.

퍼널 분석은 한글로 깔데기 분석이라고 하는데, 예를 들어 사용자가 가입한 후에, 쇼핑몰의 상품 정보를 보고  주문 및 결재를 하는 단계 까지 각 단계별로 사용자가 이탈하게 된다. 이 구조를 그려보면 깔데기 모양이 되는데,사용자 가입에서 부터 최종 목표인 주문 결재까지 이루도록 단계별로 이탈율을 분석하여 서비스를 개선하고, 이탈율을 줄이는데 사용할 수 있다.

코호트 분석은 데이타를 집단으로 나누어서 분석하는 방법으로 일일 사용자 데이타 (DAU:Daily Active User)그래프가 있을때, 일일 사용자가 연령별로 어떻게 분포가 되는지등을 나눠서 분석하여 데이타를 조금 더 세밀하게 분석할 수 있는 방법이다.


이러한 코호트 분석과 퍼넬 분석은 모바일 데이타 분석 플랫폼 중에서 일부만 지원하는데, 파이어베이스 애널러틱스는 퍼넬과 코호트 분석을 기본적으로 제공하고 있으며, 특히 코호트 분석으로 많이 사용되는 사용자 잔존율 (Retention 분석)의 경우 별다른 설정 없이도 기본으로 제공하고 있다.


<그림. 구글 파이어베이스의 사용자 잔존율 코호트 분석 차트>

출처 : https://support.google.com/firebase/answer/6317510?hl=en

무제한 앱 및 무제한 사용자 무료 지원

이러한 모바일 서비스 분석 서비스의 경우 사용자 수나 수집할 수 있는 이벤트 수나 사용할 수 있는 앱수에 제약이 있는데, 파이어베이스 애널러틱스의 경우에는 제약이 없다.

빅쿼리 연계 지원

가장 강력한 기능중의 하나이자, 이 글에서 주로 다루고자 하는 내용이 빅쿼리 연동 지원이다.

모바일 데이타 분석 서비스 플랫폼의 경우 대 부분 플랫폼 서비스의 형태를 띄기 때문에, 분석 플랫폼에서 제공해주는 일부 데이타만 볼 수 가 있고, 원본 데이타에 접근하는 것이 대부분 불가능 하다.

그래서 모바일 애플리케이션 서버에서 생성된 데이타나, 또는 광고 플랫폼등 외부 연동 플랫폼에서 온 데이타에 대한 연관 분석이 불가능하고, 원본 데이타를 통하여 여러가지 지표를 분석하는 것이 불가능하다.


파이어베이스 애널러틱스의 경우에는 구글의 데이타 분석 플랫폼이 빅쿼리 연동을 통하여 모든 데이타를 빅쿼리에 저장하여 간단하게 분석이 가능하다.

구글 빅쿼리에 대한 소개는 http://bcho.tistory.com/1116 를 참고하기 바란다.

구글의 빅쿼리는 아마존 S3나, 구글의 스토리지 서비스인 GCS 보다 저렴한 비용으로 데이타를 저장하면서도, 수천억 레코드에 대한 연산을 수십초만에 8~9000개의 CPU와 3~4000개의 디스크를 사용해서 끝낼만큼 어마어마한 성능을 제공하면서도, 사용료 매우 저렴하며 기존 SQL 문법을 사용하기 때문에, 매우 쉽게 접근이 가능하다.

모바일 데이타 분석을 쉽게 구현이 가능

보통 모바일 서비스에 대한 데이타 분석을 할때는 무료 서비스를 통해서 DAU나 세션과 같은 기본적인 정보 수집은 가능하지만, 추가적인 이벤트를 수집하여 저장 및 분석을 하거나 서버나 다른 시스템의 지표를 통합 분석 하는 것은 별도의 로그 수집 시스템을 모바일 앱과 서버에 만들어야 하였고, 이를 분석 및 저장하고 리포팅 하기 위해서 하둡이나 스파크와 같은 복잡한 빅데이타 기술을 사용하고 리포팅에도 많은 시간이 소요 되었다.


파이어베이스 애널러틱스를 이용하면, 손 쉽게, 추가 이벤트나 로그 정보를 기존의 로깅 프레임웍을 통하여 빅쿼리에 저장할 수 있고, 복잡한 하둡이나 스파크의 설치나 프로그래밍 없이 빅쿼리에서 간략하게 SQL만을 사용하여 분석을 하고 오픈소스 시각화 도구인 Jupyter 노트북이나 구글의 데이타스튜디오 (http://datastudio.google.com)을 통하여 시작화가 간단하기 때문에, 이제는 누구나 쉽게 빅데이타 로그를 수집하고 분석할 수 있게 된다.

실시간 데이타 분석은 지원하지 않음

파이어베이스 애널러틱스가 그러면 만능 도구이고 좋은 기능만 있는가? 그건 아니다. 파이어베이스 애널러틱스는 아직까지는 실시간 데이타 분석을 지원하고 있지 않다. 수집된 데이타는 보통 수시간이 지나야 대쉬 보드에 반영이 되기 때문에 현재 접속자나, 실시간 모니터링에는 적절하지 않다.

그래서 보완을 위해서 다른 모니터링 도구와 혼용해서 사용하는 게 좋다. 실시간 분석이 강한 서비스로는 트위터 fabric이나 Google analytics 등이 있다.

이러한 도구를 이용하여 데이타에 대한 실시간 분석을 하고, 정밀 지표에 대한 분석을 파이어베이스 애널러틱스를 사용 하는 것이 좋다.


파이어베이스 애널러틱스 적용해보기

백문이 불여일견이라고, 파이어베이스 애널러틱스를 직접 적용해보자.

https://firebase.google.com/ 사이트로 가서, 가입을 한 후에, “콘솔로 이동하기"를 통해서 파이어 베이스 콘솔로 들어가자.

프로젝트 생성하기

다음으로 파이어베이스 프로젝트를 생성한다. 상단 메뉴에서 “CREATE NEW PROJECT”를 선택하면 새로운 파이어 베이스 프로젝트를 생성할 수 있다. 만약에 기존에 사용하던 구글 클라우드 프로젝트등이 있으면 별도의 프로젝트를 생성하지 않고 “IMPORT GOOGLE PROJECT”를 이용하여 기존의 프로젝트를 불러와서 연결할 수 있다.



프로젝트가 생성되었으면 파이어베이스를 사용하고자 하는 앱을 등록해야 한다.

파이어베이스 화면에서 “ADD APP” 이라는 버튼을 누르면 앱을 추가할 수 있다.

아래는 앱을 추가하는 화면중 첫번째 화면으로 앱에 대한 기본 정보를 넣는 화면이다.

“Package name” 에, 파이어베이스와 연동하고자 하는 안드로이드 앱의 패키지 명을 넣는다.


ADD APP 버튼을 누르고 다음 단계로 넘어가면 google-services.json 이라는 파일이 자동으로 다운된다. 이 파일은 나중에 안드로이드 앱의 소스에 추가해야 하기 때문에 잘 보관한다.


Continue 버튼을 누르면 아래와 같이 다음 단계로 넘어간다. 다음 단계에서는 안드로이드 앱을 개발할때 파이어베이스를 연동하려면 어떻게 해야 하는지에 대한 가이드가 나오는데, 이 부분은 나중에 코딩 부분에서 설명할 예정이니 넘어가도록 하자.


자 이제 파이어베이스 콘솔에서, 프로젝트를 생성하고 앱을 추가하였다.

이제 연동을 할 안드로이드 애플리케이션을 만들어보자.

안드로이드 빌드 환경 설정

콘솔에서 앱이 추가되었으니, 이제 코드를 작성해보자, 아래 예제는 안드로이드 스튜디오 2.1.2 버전 (맥 OS 기준) 으로 작성되었다.


먼저 안드로이드 프로젝트를 생성하였다. 이때 반드시 안드로이드 프로젝트에서 앱 패키지 명은 앞에 파이어베이스 콘솔에서 지정한 com.terry.hellofirebase가 되어야 한다.

안드로이드 프로젝트에는 프로젝트 레벨의 build.gradle 파일과, 앱 레벨의 build.gradle 파일이 있는데



프로젝트 레벨의 build.gradle 파일에 classpath 'com.google.gms:google-services:3.0.0' 를 추가하여  다음과 같이 수정한다.


// Top-level build file where you can add configuration options common to all sub-projects/modules.


buildscript {

  repositories {

      jcenter()

  }

  dependencies {

      classpath 'com.android.tools.build:gradle:2.1.2'

      classpath 'com.google.gms:google-services:3.0.0'

      // NOTE: Do not place your application dependencies here; they belong

      // in the individual module build.gradle files

  }

}


allprojects {

  repositories {

      jcenter()

  }

}


task clean(type: Delete) {

  delete rootProject.buildDir

}



다음으로, 앱레벨의 build.gradle 파일도 dependencies 부분에    compile 'com.google.firebase:firebase-core:9.4.0' 를 추가하고, 파일 맨 아래 apply plugin: 'com.google.gms.google-services' 를 추가 하여 아래와 같이 수정한다.

apply plugin: 'com.android.application'


android {

  compileSdkVersion 24

  buildToolsVersion "24.0.2"


  defaultConfig {

      applicationId "com.terry.hellofirebase"

      minSdkVersion 16

      targetSdkVersion 24

      versionCode 1

      versionName "1.0"

  }

  buildTypes {

      release {

          minifyEnabled false

          proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

      }

  }

}


dependencies {

  compile fileTree(dir: 'libs', include: ['*.jar'])

  testCompile 'junit:junit:4.12'

  compile 'com.android.support:appcompat-v7:24.2.0'

  compile 'com.google.firebase:firebase-core:9.4.0'

}

apply plugin: 'com.google.gms.google-services'



그리고 파이어베이스 콘솔에서 앱을 추가할때 다운된 google-services.json 파일을 app디렉토리에 복사한다.




이 예제의 경우에는 /Users/terrycho/AndroidStudioProjects/HelloFireBase에 프로젝트를 만들었기 때문에,  /Users/terrycho/AndroidStudioProjects/HelloFireBase/app 디렉토리에 복사하였다.


Gradle 파일 수정이 끝나고, google-services.json 파일을 복사하였으면 안드로이드 스튜디오는 gradle 파일이 변경이 되었음을 인지하고 sync를 하도록 아래 그림과 같이 “Sync now”라는 버튼이 상단에 표시된다.


“Sync now”를 눌러서 프로젝트를 동기화 한다.

예제 코드 만들기

이제 안드로이드 스튜디오의 프로젝트 환경 설정이 완료되었다. 이제, 예제 코드를 만들어 보자.

이 예제 코드는 단순하게, 텍스트 박스를 통해서 아이템 ID,이름, 그리고 종류를 입력 받아서, 파이어베이스 애널러틱스에 이벤트를 로깅하는 예제이다.

파이어베이스 애널러틱스 서버로 로그를 보낼 것이기 때문에, AndroidManifest 파일에 아래와 같이  수정하여 INTERNET과 ACCESS_NETWORK_STATE 권한을 추가한다.

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

  package="com.terry.hellofirebase">

  <uses-permission android:name="android.permission.INTERNET" />

  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

 

  <application

      android:allowBackup="true"

      android:icon="@mipmap/ic_launcher"

      android:label="@string/app_name"

      android:supportsRtl="true"

      android:theme="@style/AppTheme">

      <activity android:name=".MainActivity">

          <intent-filter>

              <action android:name="android.intent.action.MAIN" />


              <category android:name="android.intent.category.LAUNCHER" />

          </intent-filter>

      </activity>

  </application>


</manifest>


다음으로 화면을 구성해야 하는데, 우리가 구성하려는 화면 레이아웃은 대략 다음과 같다.



각각의 EditText 컴포넌트는 tv_contentsId, tv_contentsName,tv_contentsCategory로 지정하였다.

위의 레이아웃을 정의한 activity_main.xml은 다음과 같다.


<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

  xmlns:tools="http://schemas.android.com/tools"

  android:layout_width="match_parent"

  android:layout_height="match_parent"

  android:paddingBottom="@dimen/activity_vertical_margin"

  android:paddingLeft="@dimen/activity_horizontal_margin"

  android:paddingRight="@dimen/activity_horizontal_margin"

  android:paddingTop="@dimen/activity_vertical_margin"

  tools:context="com.terry.hellofirebase.MainActivity">


  <LinearLayout

      android:orientation="vertical"

      android:layout_width="match_parent"

      android:layout_height="match_parent"

      android:layout_alignParentLeft="true"

      android:layout_alignParentStart="true">


      <TextView

          android:layout_width="wrap_content"

          android:layout_height="wrap_content"

          android:textAppearance="?android:attr/textAppearanceMedium"

          android:text="Contents ID"

          android:id="@+id/tv_contetnsId" />


      <EditText

          android:layout_width="match_parent"

          android:layout_height="wrap_content"

          android:id="@+id/txt_contentsId"

          android:layout_gravity="center_horizontal" />


      <TextView

          android:layout_width="wrap_content"

          android:layout_height="wrap_content"

          android:textAppearance="?android:attr/textAppearanceMedium"

          android:text="Contents Name"

          android:id="@+id/tv_contentsName" />


      <EditText

          android:layout_width="match_parent"

          android:layout_height="wrap_content"

          android:id="@+id/txt_contentsName" />


      <TextView

          android:layout_width="wrap_content"

          android:layout_height="wrap_content"

          android:textAppearance="?android:attr/textAppearanceMedium"

          android:text="Contents Category"

          android:id="@+id/tv_contentsCategory" />


      <EditText

          android:layout_width="match_parent"

          android:layout_height="wrap_content"

          android:id="@+id/txt_contentsCategory" />


      <Button

          android:layout_width="wrap_content"

          android:layout_height="wrap_content"

          android:text="Send Event"

          android:id="@+id/btn_sendEvent"

          android:layout_gravity="center_horizontal"

          android:onClick="onSendEvent" />

  </LinearLayout>

</RelativeLayout>


레이아웃 설계가 끝났으면, SEND EVENT 버튼을 눌렀을때, 이벤트를 파이어베이스 애널러틱스 서버로 보내는 코드를 만들어 보자.

MainActivity인 com.terry.hellofirebase.MainActivity 클래스의 코드는 다음과 같다.


package com.terry.hellofirebase;


import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.view.View;

import android.widget.EditText;

import android.widget.Toast;


import com.google.firebase.analytics.FirebaseAnalytics;


public class MainActivity extends AppCompatActivity {


  // add firebase analytics object

  private FirebaseAnalytics mFirebaseAnalytics;


  @Override

  protected void onCreate(Bundle savedInstanceState) {

      super.onCreate(savedInstanceState);

      mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);

      setContentView(R.layout.activity_main);

  }


  public void onSendEvent(View view){

      String contentsId;

      String contentsName;

      String contentsCategory;


      EditText txtContentsId = (EditText)findViewById(R.id.txt_contentsId);

      EditText txtContentsName = (EditText)findViewById(R.id.txt_contentsName);

      EditText txtContentsCategory = (EditText)findViewById(R.id.txt_contentsCategory);


      contentsId = txtContentsId.getText().toString();

      contentsName = txtContentsName.getText().toString();

      contentsCategory = txtContentsCategory.getText().toString();


      Bundle bundle = new Bundle();

      bundle.putString(FirebaseAnalytics.Param.ITEM_ID, contentsId);

      bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, contentsName);

      bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, contentsCategory);

      mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle);


      Toast.makeText(getApplicationContext(), "Sent event", Toast.LENGTH_LONG).show();

  }

}


MainActivity 클래스에 FirebaseAnalytics 객체를 mFirebaseAnalytics라는 이름으로 정의하고 onCreate메서드에서 FirebaseAnalytics.getInstance(this) 메서드를 이용하여 파이어베이스 애널러틱스 객체를 생성한다.


다음 onSendEvent라는 메서드를 구현한다. 이 메서드는 화면에서 “SEND EVENT”라는 버튼을 누르면 EditText 박스에서 입력된 값으로 SELECT_CONTENT라는 이벤트를 만들어서 파이어베이스 애널러틱스 서버로 보내는 기능을 한다.

컨텐츠 ID,NAME,CATEGORY를 EditText 박스에서 읽어온 후에, Bundle 이라는 객체를 만들어서 넣는다.

파이어베이스 애널러틱스 로그는 이벤트와 번들이라는 개념으로 구성이 된다.

이벤트는 로그인, 컨텐츠 보기, 물품 구매와 같은 이벤트이고, Bundle은 이벤트에 구체적인 인자를 묶어서 저장하는 객체이다. 위의 예제인 경우 SELECT_CONTENTS 라는 이벤트가 발생할때 컨텐츠 ID, 이름(Name), 종류(Category)를 인자로 하여, Bundle에 묶어서 전달하도록 하였다.

Bundle 클래스를 생성한후, bundle.putString(“인자명",”인자값") 형태로 Bundle 객체를 설정한 후에, mFirebaseAnalytics.logEvent(“이벤트명",”Bundle 객체") 메서드를 이용하여 SELECT_CONTENTS 이벤트에 앞서 작성한 Bundle을 통하여 인자를 전달하였다.


앱 개발이 모두 완료되었다. 이제 테스트를 해보자

실행하기

앱을 실행하고 아래와 같이 데이타를 넣어보자


컨텐츠 ID는 200, 컨텐츠 이름은 W, 그리고 컨텐츠 종류는 webtoon으로 입력하였다.

SEND EVENT 눌러서 이벤트를 보내서 파이어베이스 웹콘솔에 들어가서 Analytics 메뉴에 상단 메뉴인 “Events”를 선택하면 처음에는 아무런 값이 나오지 않는다.

앞에서 설명했듯이 파이어베이스 애널러틱스는 아직까지 실시간 분석을 지원하지 않기 때문에 수시간이 지난 후에야 그 값이 반영 된다.


본인의 경우 밤 12시에 테스트를 진행하고 아침 9시경에 확인을 하였더니 아래와 같은 결과를 얻을 수 있었다.



실제로 테스트 시에 select contents 이벤트를 3번을 보냈더니, Count가 3개로 나온다.

그러나 이벤트에 보낸 컨텐츠 ID, 이름 , 분류등은 나타나지 않는다. 기본 설정에서는 이벤트에 대한 디테일 정보를 얻기가 어렵다. 그래서 빅쿼리 연동이 필요한데 이는 후에 다시 다루도록 하겠다.


Dashboard 메뉴를 들어가면 다음과 같이 지역 분포나 단말명등 기본적인 정보를 얻을 수 있다.



이벤트와 이벤트 인자

앞서처럼 이벤트와 인자등을 정해줬음에도 불구하고 대쉬보드나 기타 화면에 수치들이 상세하지 않은 것을 인지할 수 있다. 정확한 데이타를 분석하려면 마찬가지로 정확한 데이타를 보내줘야 하는데, 화면 로그인이나 구매등과 같은 앱에서의 이벤트를 앱 코드내에 삽입해줘야 상세한 분석이 가능하다.

이벤트는 https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Event 에 정의가 되어 있고, 각 이벤트별 인자에 대한 설명은 https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Param 에 있는데, 이미 파이어베이스에서는 게임이나 미디어 컨텐츠, 쇼핑과 같은 주요 모바일 앱 시나리오에 대해서 이벤트와 인자들은 미리 정의해놓았다.

https://support.google.com/firebase/topic/6317484?hl=en&ref_topic=6386699

를 보면 모바일 앱의 종류에 따라서 어떠한 이벤트를 사용해야 하는지가 정의되어 있다.


또한 미리 정의되어 있는 이벤트 이외에도 사용자가 직접 이벤트를 정의해서 사용할 수 있다.  이러한 이벤트를 커스텀 이벤트라고 하는데 https://firebase.google.com/docs/analytics/android/events 를 참고하면 된다.


지금까지 간략하게 나마 파이어베이스 애널러틱스의 소개와 예제 코드를 통한 사용 방법을 알아보았다.

모바일 데이타 분석이나 빅데이타 분석에서 가장 중요한 것은 데이타를 모으는 것도 중요하지만, 모아진 데이타에 대한 지표 정의와 그 의미를 파악하는 것이 중요하다. 그래서 다음 글에서는 파이어베이스 애널러틱스에 정의된 이벤트의 종류와 그 의미 그리고, 대쉬 보드를 해석하는 방법에 대해서 설명하고, 그 후에 빅쿼리 연동을 통해서 상세 지표 분석을 하는 방법에 대해서 소개하고자 한다.


저작자 표시 비영리
신고

빠르게 훝어 보는 node.js - mongoose 스키마와 유용한 기능


조대협 (http://bcho.tistory.com)

지난번 mongoose 에 대한 간략한 소개 글 http://bcho.tistory.com/1094 에 이어서 오늘은 mongoose 스키마와, 기타 유용한 기능에 대해서 소개하고자 한다.

 쿼리

 

간단한 삽입,삭제,수정,조회 쿼리이외에 조금 향상된 쿼리를 살펴보자.

자세한 쿼리 사용 방법은 http://mongoosejs.com/docs/documents.html 참고하면 된다.

 

가지 쿼리들을 살펴보면

 

 

var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/mydb');

var userSchema = mongoose.Schema({

      userid: String,

      sex : String,

      city : String,

      age : Number

});

 

var User = mongoose.model('users',userSchema);

 

// select city from users where userid='terry'

User.findOne({'userid':'terry'}).select('city').exec(function(err,user){

      console.log("q1");

      console.log(user+"\n");

      return;

});

 

 

// select * from users where city='seoul' order by userid limit 5

User.find({'city':'seoul'}).sort({'userid':1}).limit(5).exec(function(err,users){

      console.log("q2");

      console.log(users+"\n");

      return;

});

 

// using JSON doc query

// select userid,age from users where city='seoul' and age > 10 and age < 29

User.find({'city':'seoul', 'age':{$gt:10 , $lt:29}})

      .sort({'age':-1})

      .select('userid age')

      .exec(function(err,users){

           console.log("q3");

           console.log(users+"\n");

           return;

});

 

//using querybuilder

//select userid,age from users where city='seoul' and age > 10 and age < 29

User.find({})

      .where('city').equals('seoul')

      .where('age').gt(10).lt(29)

      .sort({'age':-1})

      .select('userid age')

      .exec(function(err,users){

           console.log("q4");

           console.log(users+"\n");

           return;

});

 

Figure 20 mongoose 쿼리 예제

 

코드 부분에서는

var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/mydb');

var userSchema = mongoose.Schema({

      userid: String,

      sex : String,

      city : String,

      age : Number

});

 

var User = mongoose.model('users',userSchema);

 

mongoose 모듈을 로딩하고, mongodb 연결한다. 다음 스키마를 정의 한다. userid, sex, city 문자열로 가지고, age라는 필드를 숫자로 갖는다. 스키마 정의가 끝났으면 스키마를 이용해서 users 컬렉션에 대해서 User 모델 객체를 생성한다.

 

첫번째 쿼리를 살펴보자

// select city from users where userid='terry'

User.findOne({'userid':'terry'}).select('city').exec(function(err,user){

      console.log("q1");

      console.log(user+"\n");

      return;

});

 

첫번째 쿼리는 userid terry 도큐먼트를 쿼리해서 그중에서 city 라는 이름의 필드만 리턴하는 쿼리이다.

실행 결과는 다음과 같다.

 

q1

{ city: 'seoul', _id: 56e62f2c1a2762d26afa6053 }

 

두번째 쿼리를 살펴보자

// select * from users where city='seoul' order by userid limit 5

User.find({'city':'seoul'}).sort({'userid':1}).limit(5).exec(function(err,users){

      console.log("q2");

      console.log(users+"\n");

      return;

});

 

다음은 실행결과이다.

 

q2

{ age: 18,

  city: 'seoul',

  sex: 'female',

  userid: 'cath',

  _id: 56e62f351a2762d26afa6054 },{ age: 23,

  city: 'seoul',

  sex: 'female',

  userid: 'stella',

  _id: 56e62f3c1a2762d26afa6055 },{ age: 29,

  city: 'seoul',

  sex: 'male',

  userid: 'terry',

  _id: 56e62f2c1a2762d26afa6053 },{ age: 27,

  city: 'seoul',

  sex: 'female',

  userid: 'yuna',

  _id: 56e62f411a2762d26afa6056 }

 

users 컬렉션에서 city 필드가 seoul 도큐먼트를 조회한후 결과를 sort({‘userid’:1}) 이용하여 userid 따라 오름차순으로 정렬한 후에, limit(5) 이용하여 결과중 5개만 리턴한다.

 

세번째 쿼리를 보면

// using JSON doc query

// select userid,age from users where city='seoul' and age > 10 and age < 29

User.find({'city':'seoul', 'age':{$gt:10 , $lt:29}})

      .sort({'age':-1})

      .select('userid age')

      .exec(function(err,users){

           console.log("q3");

           console.log(users+"\n");

           return;

});                                 

 

users 컬렉션에서 city seoul이고, age 10보다 크고, 29보다 작은 도큐먼트를 조회한 sort 이용하여 내림 차순으로 정렬을 , select 이용하여 userid age 필드만 리턴한다

다음은 쿼리 실행결과이다.

 

q3

{ age: 23, userid: 'stella', _id: 56e62f3c1a2762d26afa6055 },{ age: 18, userid: 'cath', _id: 56e62f351a2762d26afa6054 }

 

마지막 네번째 쿼리를 보면

//using querybuilder

//select userid,age from users where city='seoul' and age > 10 and age < 29

User.find({})

      .where('city').equals('seoul')

      .where('age').gt(10).lt(29)

      .sort({'age':-1})

      .select('userid age')

      .exec(function(err,users){

           console.log("q4");

           console.log(users+"\n");

           return;

});

 

세번째 쿼리와 내용을 같으나, 검색 조건을 find() 안에서 json으로 정의하지 않고, 밖에 where문으로 빼서 정의했다. 이를 쿼리 빌더라고 하는데, mongoose where문에 따라서 자동으로 쿼리를 생성해준다.

.where('city').equals('seoul') city필드가 seoul 도큐먼트를 조회하고

.where('age').gt(10).lt(29) age 10보다 크고, 29보다 작은 도큐먼트를 조회하도록 한다.

다음은 쿼리 수행 결과이다.

 

q4

{ age: 23, userid: 'stella', _id: 56e62f3c1a2762d26afa6055 },{ age: 18, userid: 'cath', _id: 56e62f351a2762d26afa6054 }

 

예제 코드는 https://github.com/bwcho75/nodejs_tutorial/tree/master/mongoosequeryexample 저장되어 있다.

 

 데이타 유효성 검증

 

mongoose 가지고 있는 유용한 기능중의 하나가 validator라는 데이타 유효성 검증이다.

모델 객체에 들어갈 데이타 형뿐 아니라, 데이타의 규약등을 지정할 있는 기능인데, 예를 들어 문자열의 길이나, 특수문자 지원 여부등을 검증할 있다.

 

앞에서 만들었던 mongo.js에서 userSchema 부분을 다음과 같이 수정해보자

var mongoose = require('mongoose');

//define validator

function NameAlphabeticValidator(val){

    return val.match("^[a-zA-Z\(\)]+$");

}

function StringLengthValidator(val){

    if(val.length>10) return null;

    return val;

}

 

//define scheme

var userSchema = mongoose.Schema({

      userid: {type:String,validate:NameAlphabeticValidator},

      sex : String,

      city : {type:String,validate:[

                                    {validator:NameAlphabeticValidator,msg:'city should be alphabet only'},

                                    {validator:StringLengthValidator,msg:'city length should be less than 10'}

                                   ]

              }

      });

Figure 21 mongoose validator 예제

 

개의 validator 정의하였다. 하나는 알파벳만 허용하는 NameAlphabeticValidator이고, 다른 하나는 문자열의 길이가 10 이하인 것만 통과 시키는 StringLengthValidator이다.

Validator 정의는 간단하게 function(value)형태로 정의한다. 검증하고자 하는 값이 value라는 인자를 통해서 들어오고, 만약 검증을 통과하면 값을 리턴하면 되면, 실패하면 null 리턴하면 된다.

 

선언된 validator 스키마에 적용해보자.

validator 적용하는 방법은 스키마에서 필드의 데이타 타입을 지정하는 부분에서 위와 같이 데이타 타입을 지정한 , 뒷부분에 validate라는 키워드를 이용하여, 앞서 정의한 validator 명을 지정해주면 된다.

 

userid: {type:String,validate:NameAlphabeticValidator},

 

또는 다음과 같이 하나의 데이타 필드에 배열[] 이용하여 동시에 여러개의 validator 적용할 있다.

다음 코드는 city 필드에 NameAlphabeticValidatorStringLengthValidator 두개를 동시에 적용한 코드이다.

city : {type:String,validate:[

                                    {validator:NameAlphabeticValidator,msg:'city should be alphabet only'},

                                    {validator:StringLengthValidator,msg:'city length should be less than 10'}

                                   ]

 

validator 지정할때 위의 예제와 같이 msg 같이 정의하면, 데이타에 대한 유효성 검증이 실패했을때 나는 메세지를 정의할 있다.

 

다음은 예제에서 city이름에 10자이상의 문자열을 넣는 화면이다.

validator 의해서 유효성 검증이 실패하고, console.log 에러 메세지가 출력된 내용이다.



Figure 22 city 필드에 10자가 넘는 문자열을 입력하는 화면

 

다음은 validator 의해서 city 필드의 유효성 검사가 실패하고, console.log 에러 메세지가 출력된 화면이다.

 



Figure 23 validator에 의해서 city 필드 유효성 검증이 실패한 결과

 

이렇게 validator 만들어 사용하는 이외에도, mongoose에서는 데이타 타입별로 미리 정해놓은 validator 들이 있다.

예를 들어 Number 타입의 경우 min,max 예약어를 이용하여 타입 정의시 값의 유효 범위를 지정해놓을 있다.

age:     { type: Number, min: 18, max: 65 },

String 경우 RegularExpression 이용해서 문자열의 형태를 지정할 있고, maxlength 이용하여 전체 문자열 길이에 대한 제약을 있다.  데이타 타입별로 미리 정의되어 있는 validator http://mongoosejs.com/docs/schematypes.html 참고하기 바란다.

 

 SetterGetter, Default


mongoose에서는 스키마의 필드에 대해서 Setter Getter 저장할 있고, 데이타를 저장하지 않았을 경우에는 디폴트 값을 지정할 있다.

Setter 데이타 객체에 데이타를 저장할때, 실행되는 메서드로 데이타를 저장하기전 변환하는 역할을 수행할 있다.

아래 코드를 보자

 

var mongoose = require('mongoose');

 

// setter function

function upperCase (val) {

        return val.toUpperCase();

      }

 

var HelloSchema = new mongoose.Schema(

           { name : { type:String, default:'Hello Terry',set:upperCase} }

           );

 

// default test

var Hello = mongoose.model('hello',HelloSchema);

var hello = new Hello();

 

console.log(hello);

 

// setter test

hello.name="Upper case setter example";

console.log(hello);

 

Figure 24 mongoose setter 예제

 

{ name : { type:String, default:'Hello Terry',set:upperCase} } 코드 부분을 보면 “default”라는 키워드로 “Hello Terry” 라는 값을 지정하였다. name 필드는 별도의 값이 지정되지 않으면 “Hello Terry”라는 문자열을 디폴트 값으로 갖는다.

 

다음 set:upperCase , Setter 지정하는 부분으로, Setter “set:{Setter 함수}” 명으로 지정한다. 여기서 사용된 Setter 위에 코드에서 정의한 upperCase 라는 함수로, 값을 지정하면 문자열의 모든 알파벳을 대문자로 바꿔서 저장한다.

 

위의 예제 실행 결과를 보자

 

{ name: 'Hello Terry', _id: 56f94e5da92daa3a977d8525 }

{ name: 'UPPER CASE SETTER EXAMPLE',

  _id: 56f94e5da92daa3a977d8525 }

Figure 25 mongoose setter 예제 실행 결과

 

처음에는 아무 값도 지정하지 않았기 때문에 name 필드에 디폴트 값인 “Hello Terry” 저장된다.

다음으로, hello.name="Upper case setter example"; 저장을 했지만, 지정된 Setter 의해서, name 모든 알파벳이 대문자로 변환되어 { name: 'UPPER CASE SETTER EXAMPLE',_id: 56f94e5da92daa3a977d8525 } 저장된것을 확인할 있다.

 

Setter이외에 저장된 데이타를 조회할때, 변환하는 Getter 역시 지정이 가능하다.

다음 코드를 보자

 

var mongoose = require('mongoose');

 

// setter function

function lowercase (val) {

        return val.toLowerCase();

      }

 

var HelloSchema = new mongoose.Schema(

           { name : { type:String,get:lowercase} }

           );

 

// gettert test

var Hello = mongoose.model('hello',HelloSchema);

var hello = new Hello();

hello.name="LOWER case setter example";

console.log(hello);

console.log(hello.name);

 

Figure 26 mongoose getter 예제

 

Getter 지정은 스키마에서 타입 지정시 “get:{Getter 함수명}” 식으로 지정하면 된다. 위의 예제에서는

{ name : { type:String,get:lowercase} }

같이 lowercase 함수를 Getter 지정하였다.

 

예제에 대한 실행 결과를 보면 다음과 같다.

 

 

{ _id: 56f94f4314540b3d97fe17b3,

  name: 'LOWER case setter example' }

lower case setter example

 

Figure 27 mongoose getter 예제 실행 결과

 

실제로 데이타 객체내에 name 필드에 저장된 값은 name: 'LOWER case setter example' 이지만, hello.name으로 해당 필드의 내용을 조회했을 경우 getter 지정된 lowercase 함수를 통해서 모두 소문자로 변환된 lower case setter example

 문자열을 리턴하는 것을 확인할 있다.

 

이렇게 직접 getter setter 대한 함수를 정의할 있지만, mongoose에는 모든 문자열을 소문자로 변경하는 lowercase setter, 문자열 앞뒤의 공백을 없애주는 trim setter 등이 기본적으로 제공된다.

 

Lowercase setter 사용예

var s = new Schema({ email: { type: String, lowercase: true }})

 

trim setter 사용예

var s = new Schema({ name: { type: String, trim: true }})

 

데이타 타입별로 미리 제공되는 Setterd Getter http://mongoosejs.com/docs/schematypes.html 참고하기 바란다.

 

 스키마 타입

 

앞서서 mongoose 스키마에 대해서 설명하였는데, 조금 자세하게 살펴보자 스키마에서는 필드에 대한 데이타 타입을 정의할 있는데, 다음과 같다.

 

스키마 타입

설명

예제

String

문자열

‘Hello’

Number

숫자

135090

Date

날짜

ISODate("1970-06-09T15:00:00.000Z")

Buffer

바이너리 타입 (파일등을 저장할때 사용됨)

파일등의 바이너리 데이타

Mixed

특별한 형을 가지지 않고 아무 JSON 문서나 있음

‘any’:{ ‘data’:’this is any data….’}

Objectid

mongoDB objectid

ObjectId("56f8d0b63ef9d003961e5f3f")

Array

배열

[‘Hello’ , ‘Terry’ ]

Figure 28 mongoose 스키마 타입

설명을 돕기 위해서 예제를 보자.

다음과 같은 형태 데이타를 표현하기 위한 스키마를 저장할 것이다.

사용자의 정보를 저장하는 Profile이라는 형태의 스키마이다.

 

{

    "_id" : ObjectId("56f93d08253b92b296080587"),

    "meta" : {

        "book" : "architecture design",

        "company" : "cloud consulting"

    },

    "birthday" : ISODate("1970-06-09T15:00:00.000Z"),

    "address" : {

        "_id" : ObjectId("56f8d0b63ef9d003961e5f40"),

        "zipcode" : 135090,

        "city" : "youngin",

        "state" : "Kyungki"

    },

    "name" : "terry",

    "recommend" : [

        "I want to recommend terry",

        "He is good guy"

    ],

    "image" : {

        "data" : { "$binary" : "/9j/4AAQSkZJ (중략) Rg ", "$type" : "00" },

        "contentsType" : "image/png"

    },

    "__v" : 0

}

Figure 29 사용자 프로파일 JSON 도큐먼트 예제

 

이름, 생년월일, 주소, 그리고 사용자에 대한 추천글과, 사용자에 대한 이미지 파일을 저장하는 스키마이다.

이를 스키마로 지정하면 다음과 같다.

 

// define scheme

var addressSchema = new mongoose.Schema({

      zipcode : Number,

      city : String,

      state : String

});

 

 

var profileSchema = new mongoose.Schema({

      name : String,

      address : addressSchema,

      birthday : Date,

      meta : mongoose.Schema.Types.Mixed,

      image : {

           data : Buffer,

           contentsType : String

      },

      recommend : [String]

});

 

Figure 30 mongoose를 이용하여 schema.js 예제에서 사용자 프로파일 스키마를 정의한 부분

 

주소를 저장하기 위한 스키마는 addressSchema, 숫자로된 zipcode, 문자열로 city state 필드를 갖는다

·         name은 문자열로 이름을 저장한다.

·         address는 서브 도큐먼트 타입으로, 앞에서 정의한 addressSchema 형을 참조한다.

·         birthday는 날짜 타입이고, 

·         meta는 메타 정보를 저장하는 필드인데, Mixed 타입이다. Mixed 타입은 앞에서도 설명하였듯이, 아무 JSON 도큐먼트나 들어갈 수 있다.

·         다음으로 imageJSON 타입으로 안에, 사진 파일을 저장하기 위해서 Buffer 형으로 data  필드를 갖고, 사진 포맷 저장을 위해서 contentsType이라는 타입을 갖는다.

·         마지막으로 recommend 필드는 사용자에 대한 추천 문자열을 배열로 갖는다.

 

서브 도큐먼트 vs 임베디드 도큐먼트 vs Mixed 타입

 

스키마를 보면, 스키마 내에 JSON 도큐먼트를 갖는 필드가 address,meta,image 3 가지가 있다. 타입의 차이점은 무엇일까?

먼저 addresss 서브 도큐먼트 (sub document) 타입으로 mongodb 저장하면 도큐먼트 형으로 저장이 되고, _id 필드를 갖는다. 부모 도큐먼트 (여기서는 profileSchema) 종속 되는 도큐먼트 형태로, 단독으로는 업데이트가 불가능하고 반드시 부모 도큐먼트 업데이트시에만 업데이트가 가능하다. 이러한 서브 도큐먼트 타입은 같은 타입의 서브 도큐먼트가 반복적으로 사용될때 타입 객체를 사용할때 사용하면 좋다.

 

다음 image 필드와 같이 스키마내에 JSON 도큐먼트 포맷을 그대로 저장하는 방식을 embeded 방식이라고 하는데, 서브 도큐먼트와는 다르게 _id 필드가 붙지 않는다. 간단하게 JSON 도큐먼트를 내장할때 사용한다.

마지막으로 meta 필드의 경우 Mixed 타입을 사용했는데, 아무 포맷의 JSON 문서가 들어갈 있다. 컬렉션 내에서 해당 필드의 JSON 도큐먼트 포맷이 각기 다를때 사용할 있으며, 포맷을 정의하지 않기 때문에 유연하게 사용할 있다.

 

스키마를 정의했으면 이제 값을 넣어서 저장해보자

 

// create model

var Profile = mongoose.model('profiles',profileSchema);

var Address = mongoose.model('address',addressSchema);

var p = new Profile();

 

// populate model

p.name = "terry";

 

// address

var a = new Address();

a.zipcode = 135090;

a.city = "youngin";

a.state = "Kyungki";

p.address = a;

 

// birthday

p.birthday = new Date(1970,05,10);

 

// meta

p.meta = { company : 'cloud consulting', book : 'architecture design'};

 

// image

p.image.contentsType='image/png';

var buffer = fs.readFileSync('/Users/terry/nick.jpeg');

p.image.data = buffer;

 

// recommend

p.recommend.push("I want to recommend terry");

p.recommend.push("He is good guy");

 

p.save(function(err,silece){

      if(err){

           cosole.log(err);

           return;

      }

      console.log(p);

});

 

Figure 31 mongoose를 이용하여 schema.js 예제에서 데이타를 저장하는 부분

 

 

값을 저장하기 위해서 모델 객체를 생성한후 Profile 대한 데이타 객체 p Address 대한 데이타 객체 a 생성하였다.

값을 저장할때는 “{데이타 객체명}.필드=형태로 저장한다.

Address 저장을 위해서 데이타 객체인 a zipcode,city,state 값을 저장한후에, p.address = a 이용해서, address 필드의 값을 채워 넣는다.

p.birthday Date형이기 때문에, new Date() 메서드를 이용해서, Date 객체를 생성하여 데이타를 저장한다.

p.meta Mixed 타입으로 직접 JSON 도큐먼트를 지정하여 저장한다.

p.image 임베디드 도큐먼트 타입으로, p.image.data, p.image.contentsType 각각의 필드에 값을 저장한다. 이때 data 필드는 Buffer 타입으로, 예제에서는 /Users/terry/nick.jpeg 라는 파일을 저장 하였다. fs.readFileSync 이용하여 인코딩 지정없이 파일을 읽게 되면, 파일 데이타를 Buffer 객체로 반환해주는데, 값을 p.image.data 지정하여 저장하였다.

그리고 마지막으로, p.recommend String 배열로, push 메서드를 이용하여 데이타를 추가 하였다.

 

데이타 객체에 모든 값이 저장되었으면 이를 mongodb 저장하기 위해서 p.save 메서드를 이용하여 저장한다.

 

다음 데이타를 수정하는 방법을 알아보자. 앞의 예제에서 저장된 Profile 도큐먼트의 _id '56f93d08253b92b296080587'  라고 하자. 아래 예제는 Profile 컬렉션에서 _id '56f93d08253b92b296080587' 도큐먼트를 찾아서 birthday 2( Date.setMonth(1) 2월이다. 0부터 시작한다.) 바꿔서 save 메서드를 이용해서 저장하는 예제이다.

 

var mongoose = require('mongoose');

var fs = require('fs');

mongoose.connect('mongodb://localhost:27017/mydb');

 

// define scheme

var addressSchema = new mongoose.Schema({

      zipcode : Number,

      city : String,

      state : String

});

 

 

var profileSchema = new mongoose.Schema({

      name : String,

      address : addressSchema,

      birthday : Date,

      meta : mongoose.Schema.Types.Mixed,

      image : {

           data : Buffer,

           contentsType : String

      },

      recommend : [String]

});

 

// create model

var Profile = mongoose.model('profiles',profileSchema);

var Address = mongoose.model('address',addressSchema);

var p = new Profile();

 

Profile.findOne({_id:'56f93d08253b92b296080587'},function(err,p){

      console.log(p);

      p.birthday.setMonth(1);

      p.save(function(err,silece){

           if(err){

                 cosole.log(err);

                 return;

           }

           console.log(p);

      });

});

 

 

Figure 32 mongoose에서 데이타를 조회하여 Date 필드를 업데이트 하는 예제

 

저장된 데이타를 robomongo 이용해서 mongodb에서 확인해보면 다음과 같다.



Figure 33 예제 실행 결과, Date 필드 수정 내용이 반영되지 않은 결과

 

기대했던 결과와는 다르게, birthday 2월로 바뀌지 않고, 처음에 생성했던 6월로 되어 있는 것을 있다.

mongoose save 메서드는 필드의 값이 변환된 것만 자동으로 인식하여 save 저장하는데, 몇몇 타입의 경우 자동으로 변경된 값을 인식하지 못한다.

Date, Mixed 필드가 그러한데, 경우에는 mongoose 에게 해당 필드의 값이 변경되었음을 강제적으로 알려줘서 변경된 값을 인식하여 저장하게 해야 한다.

이때 markedModified(“필드명”) 메서드를 사용한다. 아래 코드는 markedModified 이용하여 birthday 필드가 변경되었음을 명시적으로 알려주고, 값을 저장하도록 변경한 코드이다.

 

Profile.findOne({_id:'56f93d08253b92b296080587'},function(err,p){

      console.log(p);

      p.birthday.setMonth(1);

      p.markModified('birthday');

      p.save(function(err,silece){

           if(err){

                 cosole.log(err);

                 return;

           }

           console.log(p);

      });

});

 

Figure 34 markedModified를 이용하여 Date 필드가 수정되었음을 명시적으로 알려주도록 코드를 수정한 내용

 

위의 코드를 수정한 다음 다시 mongodb 저장된 데이타를 보면 다음과 같다.



Figure 35 markedModified 반영후, Date 필드가 정상적으로 반영된 결과

 

성공적으로 birthday 월이 2월로 변경된것을 확인할 있다.

스키마 타입 관련 예제 코드는 https://github.com/bwcho75/nodejs_tutorial/tree/master/mongooseschemeexample 참고하기 바란다.

 

나중에 시간되면, population index 보강 예정

저작자 표시 비영리
신고

node.js용 개발도구가 여러가지가 있다.

Eclipse와 IntelliJ를 개발한 JetBrain의 WebStorm 과 같은 상용툴이 있지만, Eclipse의 경우, 자바기반이라서 무겁기도 하고 속도도 느리고, WebStorm은 금전적인 부담이 있다. 근래에 MS에서 node.js용 Viual Studio 플러그인을 제공하는데 Native 기반이라서 빠르기도 하고, 무료로 사용을 할 수 있는 장점이 있다.


먼저 Visual Studio 2013 Web 버전을 설치한후 (무료)

https://nodejstools.codeplex.com/ NTVS (Node js Tool for Visual Studio) 를 설치하면 된다

소개 동영상은 https://www.youtube.com/watch?feature=player_embedded&v=W_1_UqUDx2s


설치되서 실행하는 화면



아무래도, WebStorm에 익숙해져 있어져 그런지, 약간은 불편하다. node.js를 띄우면 위의 화면 처럼 별도의 dos창에서 실행이 되지만, 그외에, 에디팅이나 자동 완성 기능이나  등은 쓸만한듯 하다.


저작자 표시
신고