카테고리 보관물: 프로그래밍

Google Cloud Functions 와 Firestore 를 이용한 사용자 확인

개요

간단한 자료를 저장해야 하는데 서버를 운영하기에는 부담이 됩니다. 배보다 배꼽이 더 큰 상황이 될 수 있습니다. 이럴 때 사용할 수 있는 것이 서버리스 서비스 입니다.

서버리스 컴퓨팅

서버리스(Serverless) 컴퓨팅은 서버가 없다는 뜻 보다는 실제 서비스를 이용하는 주체가 소유하지 않는다고 이해하는 것이 좋을 것 같습니다. 서버 없이 서비스를 할 수는 없습니다. 개인이나 회사가 서버를 소유하지 않고 사용한 만큼만 비용을 지불하는 서비스 입니다. 대표적인 것이 AWS Lamba, Azure Functions, Google Clound Functions 가 있습니다.

Google Clound Functions

Clound Functions 은 Google 에서 제공하는 서비스 입니다. 사용자가 함수를 만들고 배포해서 실행할 수 있습니다. 직접 호출할 수도 있고 특정 트리거와 연결할 수 있습니다. 이 글에서는 Node.js 를 사용해서 함수를 만들고 Firestore 에 저장되어 있는 자료와 비교해서 에뮬레이터를 통해 결과를 확인하는 방법을 알아보도록 하겠습니다.

Cloud Firestore

Cloud Firestore 는 NoSQL 문서 데이터베이스 입니다. 간단한 정보를 저장할 수 있습니다. 같은 프로젝트 내에서 Clound Function 내에서 연결해서 자료를 입력, 수정, 삭제할 수 있습니다. 비정형 자료를 자유롭게 입력할 수 있는 MongoDB 같은 형태라고 이해하시면 됩니다.

구현 기능

사용자 정보가 Firestore에 입력되어 있다고 가정합니다. 웹을 통해 JSON 형식의 자료를 함수로 POST 하면 그 정보로 사용자 존재여부를 판단합니다. 그런 후 JSON 형태의 응답을 반환하는 함수를 구현해 보도록 하겠습니다. 실제 배포하는 것은 다루지 않습니다. 배포 전 에뮬레이터를 이용한 테스트까지만 살펴봅니다. 배포는 그리 어렵지 않고 호출 주소만 달라지는 것이라서 실제 작업하시는 것에는 문제가 없을겁니다.

Clound Function 구현

문서를 확인해 보면 절차대로 설명이 잘 되어 있습니다. 문서를 한번 확인하신 후 이 글을 참고 하시면 도움이 됩니다. 함수를 설정하고 작성 후 테스트 하는 순서로 작업이 진행됩니다. 함수 실행에 문제가 없다고 판단되면 배포해서 사용하면 됩니다.

먼저 Firebase 프로젝트를 만듭니다. 이미 생성된 프로젝트를 사용해도 됩니다. Node.js 환경은 이미 구성되어 있다고 가정하겠습니다. 다음 명령어를 명령 프롬프트에서 실행해서 Firebase CLI 들 설치합니다.

npm install -g firebase-tools

그 다음 다음 명령어를 실행하면 브라우저가 열리면서 인증을 할지 확인을 합니다.

firebase login

인증 후 작업 폴더에서 다음 명령어를 실행하면 기본적인 틀이 생성됩니다.

firebase init functions

사용할 언어를 JavaScript 나 TypeScript 를 선택합니다. 이 글에서는 JavaScript를 선택하겠습니다.

JavaScript 선택

나머지 항목은 엔터를 입력해서 기본값으로 진행합니다. 작업이 완료되면 다음의 명령어를 입력합니다.

firebase init emulators

에뮬레이터를 선택하는 화면이 나타나는데 Functions, Firestore를 커서 및 스페이스바 키를 이용해서 선택합니다.

Functions, Firestore 선택

Firestore를 선택하지 않으면 “The Cloud Firestore emulator is not running, so calls to Firestore will affect production” 메시지가 나타나고 함수가 정상적으로 실행되지 않습니다.

/functions/index.js 파일에 다음과 같이 함수를 추가합니다.

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

exports.authentificateUser = functions.https.onRequest((request, response) => {
    functions.logger.info('call authentificateUser', {structuredData: true});

    var userId = request.body.userId;
    var userPassword = request.body.userPassword;

    functions.logger.info('userId : ' + userId, {structuredData: true});
    functions.logger.info('userPassword : ' + userPassword, {structuredData: true});

    let db = admin.firestore();

    db.collection('user').where('userId', '==', userId).where('userPassword', '==', userPassword).get()
        .then((snapshot) => {

            var documentId = 'NONE';
            var userName = 'NONE';
            var userEmail = 'NONE';

            if (snapshot.empty) {
                functions.logger.info('No matching documents.', {structuredData: true});
            } else {

                functions.logger.info('Matching documents.', {structuredData: true});

                let userData = snapshot.docs[0];

                documentId = userData.id;
                userName = userData.get('userName');
                userEmail = userData.get('userEmail');
            }

            var userObject = { id: documentId, email: userEmail, name: userName};

            response.json({
                response: userObject,
            })

        })
        .catch((err) => {
            console.log('Error getting documents', err);
    });
});

6행은 사용자가 로그를 기록하는 코드 입니다. 입력된 값을 확인하거나 진행되는 순서등 알고자 하는 정보를 기록합니다.

8, 9행은 함수 호출 시 JSON 형태로 전달된 자료를 가져와서 변수에 대입하는 코드입니다. 이 값을 가지고 Firestore 의 자료와 비교해서 사용자 존재여부를 판단합니다.

16행은 Firestore와 연결된 user collection 에서 입력된 값과 비교하는 코드입니다. 조건에 맞는 자료가 없으면 자료가 없다는 로그를 기록합니다. 사용자 있다면 정보를 가져와서 반환될 JSON 형식으로 출력할 변수에 할당해 줍니다. Firestore 관련 API 정보는 여기에서 확인하실 수 있습니다.

에뮬레이터 실행

명령 프롬프트에 다음 명령을 실행하면 에뮬레이터가 실행되고 테스트를 진행할 수 있습니다.

firebase emulators:start
에뮬레이터 실행

설명에 나타난 바와 같이 http://localhost:4000 주소로 브라우저로 접속해 보면 상태를 확인할 수 있습니다.

에뮬레이터 상태 화면

Firestore emulator 영역의 Go to emulator 링크를 클릭해서 나타나는 화면에 테스트 할 자료를 입력합니다.

Firestore 테스트 자료 입력

Postman 같은 프로그램으로 함수를 호출해서 결과가 나타나는지 확인합니다. 입력된 사용자 정보와 일치하면 각각의 값이 반환되고 찾을 수 없다면 모두 NONE 이 반환됩니다.

Postman 을 사용한 함수 테스트

Functions emulator 영역의 View logs 링크를 클릭해보면 호출된 내역을 확인할 수 있습니다. 사용자가 추가한 로그 정보도 같이 나타나는 것을 알 수 있습니다.

함수 호출 내역 확인

함수에 문제가 없다는 확신이 들 때까지 이와 같은 방법으로 테스트합니다. 문제가 있는 함수를 잘못 배포하면 예기치않게 많은 비용이 청구될 수 있습니다.

Google Cloud Functions, Firestore 를 이용해서 서버리스 서비스를 호출해서 사용자 인증하는 방법을 알아보았습니다. 이것을 응용하면 원하시는 함수를 제작하실 수 있을 것 입니다.

Go(Golang) 언어로 윈도우즈 서비스 프로그램 만들기

개요

주기적으로 폴더를 감시해서 파일 처리하는 프로그램을 개발하게 되었습니다. 윈도우즈 기반 서버에서 서비스 형태로 동작하는 것이 우선적인 목표였습니다. 그런데 윈도우즈가 아닌 리눅스 서버에서도 동작할 수도 있다는 조건이 추가되었습니다. 멀티 플랫폼을 지원하는 Go(Golang) 언어로 개발하게 되었습니다.

패키지

Go(Golang) 언어에서는 윈도우 서비스 프로그램 제작 기능을 기본적으로 지원하지 않습니다. 그래서 윈도우 서비스 프로그램을 제작할 수 있게 도와주는 패키지( https://github.com/kardianos/service)를 이용했습니다. 사이트에 접속해 보면 Go 프로그램을 주요 플랫폼에서 서비스 형태로 실행할 수 있게 해준다고 설명되어 있습니다. example 폴더의 파일을 참조해서 개발하시면 됩니다.

윈도우 서비스 개발

다음의 소스와 같이 작업하면 윈도우 서비스 프로그램이 됩니다. run 함수내에 작업하고자 하는 내용을 추가하시면 됩니다. 빌드 후 실행해 보면 명령 프롬프트에서 실행이 됩니다.

package main

import (
	"log"

	"github.com/kardianos/service"
)

type GoWindowsService struct{}

func (goWindowsService *GoWindowsService) Start(windowsService service.Service) error {
	go goWindowsService.run()
	return nil
}

func (goWindowsService *GoWindowsService) run() {
	// Do your work here
	log.Println("Run!")
}

func (goWindowsService *GoWindowsService) Stop(windowsService service.Service) error {
	return nil
}

func main() {
	serviceConfig := &service.Config{
		Name:        "GoWindowsService",
		DisplayName: "Go Windows service",
		Description: "Go Windows service",
	}

	goWindowsService := &GoWindowsService{}
	windowsService, err := service.New(goWindowsService, serviceConfig)
	if err != nil {
		log.Println(err)
	}

	err = windowsService.Run()
	if err != nil {
		log.Println(err)
	}
}

주의하실 사항은 main 함수에는 서비스 실행 코드만 있어야 합니다. 다른 코드가 있으면 서비스 등록 후 실행하면 다음의 오류가 발생할 수 있습니다.

서비스가 시작이나 제어 요청에 빠르게 응답하지 않았습니다.

서비스 등록

프로그램은 실행할 수 있지만 서비스 프로그램 목록에는 나타나지 않습니다. 다음과 같은 방법으로 등록 합니다. 명령 프롬프트는 관리자 권한으로 실행한 후 다음 명령을 실행합니다. 주의하실 사항은 binPath= 뒤쪽에 공백이 있어야 합니다. sc 유틸리티에 대한 설명은 여기에서 확인하실 수 있습니다.

sc create "Go Windows Service" binPath= yourpath\your_windows_service.exe
sc description "Go Windows Service" "Go Windows Service Description"
sc start "Go Windows Service"

등록 명령을 실행하면 [SC] CreateService 성공, [SC] ChangeServiceConfig2 성공 이라는 메시지가 각각 나타납니다. sc start 는 해당 서비스를 바로 실행하는 명령어 입니다. 다음과 같이 서비스 리스트에 등록된 것을 확인할 수 있습니다.

서비스 등록

서비스 삭제

다음 명령으로 서비스를 삭제합니다. sc stop 명령어는 해당 서비스를 중지합니다.

sc stop "Go Windows Service"
sc delete "Go Windows Service"

Go(Golang) 언어로 윈도우즈 서비스 프로그램을 만들고 등록, 삭제하는 방법을 알아보았습니다.