본문 바로가기

Delphi/C++Builder

[REST API][실습] 데이터셋 기반 REST API 개발하기

이 글에서는 데이터셋 기반으로 일괄 데이터 처리하는 REST API 엔드포인트를 구현하고, 연동하는 내용을 설명합니다.


데이터셋 기반 REST API

데이터셋(TDataSet)은 데이터들의 집합으로, FireDAC의 데이터 셋(TFDDataSet)은 데이터셋의 내용을 JSON 포맷으로 저장하고, 불러오는 기능을 제공합니다. 


이 기능을 활용해 REST API의 JSON 포맷을 손쉽게 개발할 수 있습니다.


데이터셋 기반 REST API의 특징(장/단점)은 다음과 같습니다.

1) (장점) 매우 신속하고, 손쉽게 REST API 서버, 클라이언트를 개발할 수 있습니다.

데이터를 JSON 포맷으로 변환하는 코드가 대단히 짧아 집니다.


2) (단점)JSON 포맷을 직접 설정할 수 없습니다.

FireDAC의 JSON 저장 기능을 이용하기 때문에 FireDAC의 JSON 포맷을 그대로 사용합니다.

필요한 항목을 추가, 변경하기 쉽지 않습니다.

또한, REST 아키텍처의 규칙과 일부 다르게 구현해야 합니다.(GET과 POST 메소드만 사용합니다.)

(개인적으로 델파이 클라이언트-서버 환경으로 개발하는 경우 활용하는 것이 좋다는 의견입니다.)


이 글에 앞서 다음 내용을 선행학습하시기 바랍니다.


이 글에서는 다음 내용을 다룹니다.

  • [이론] 데이터셋과 JSON
    • JSON 포맷 저장/불러오기
    • CachedUpdates와 TFDSchemaAdapter
  • [실습] 데이터셋 기반 REST API 개발하기
    • 데이터셋 기반 REST API 서버 개발하기
    • 데이터셋 기반 REST API 클라이언트 개발하기

이 글의 실습에서는 도서대여 프로그램 만들기에서 사용한 데이터베이스를 사용합니다. 다음 링크에서 데이터베이스 구조를 확인하고, DB 파일을 다운로드 받으시기 바랍니다.

데이터셋과 JSON

FireDAC의 데이터셋(TFDDataSet)은 JSON 포맷으로 데이터 저장하고, JSON 포맷의 데이터를 불러오는 기능을 제공합니다.


JSON 포맷 저장/불러오기

FireDAC 데이터셋(TFDDataSet)은 SaveToStream, LoadFromStream 메소드를 이용해 데이터를 저장하고 불러오는 기능을 제공합니다. 파라메터로 스트림(TStream)과 데이터포맷을 지정할 수 있으며, 데이터포맷으로는 XML, JSON, Binary 중 선택할 수 있습니다.


아래와 같은 코드로 데이터셋의 데이터를 JSON 포맷으로 저장하고, 불러올 수 있습니다.

var
  Stream: TMemoryStream;
begin
  Stream := TMemoryStream.Create;
  try
    FDQuery1.SaveToStream(Stream, TFDStorageFormat.sfJSON);

    Stream.Position := 0;
    FDQuery1.LoadFromStream(Stream, TFDStorageFormat.sfJSON);
    FDQuery1.ApplyUpdates;
  finally
    Stream.Free;
  end;

단, 데이터를 로드(LoadFromStream)의 경우 데이터셋의 CachedUpdates 속성이 True로 설정되어 있어야 합니다.


참고: 위 코드를 사용하기 위해서는 TFDStanStorageJSONLink 컴포넌트를 화면(또는 데이터모듈)에 올려놓거나, "FireDAC.Stan.StorageJSON" 유닛을 유즈절에 추가해야 합니다.


알아야 하는 주요 컴포넌트, 속성

캐쉬 업데이트 속성(CachedUpdates)

데이터셋의 CachedUpdates 속성을 사용하면 데이터셋의 Post 메소드 호출 시 데이터셋의 캐쉬(메모리)에 임시로 저장되고, ApplyUpdates 메소드 호출 시 캐쉬 데이터가 실제 DBMS에 변경된 데이터가 적용됩니다. 즉, 캐쉬 기반으로 데이터 조작 후 일괄 적용할 수 있습니다.


스키마어댑터 컴포넌트(TFDSchemaAdapter)

스키마어댑터(TFDSchemaAdapter) 컴포넌트는 중앙 캐쉬 업데이트를 지원합니다. 스키마어댑터 컴포넌트(TFDSchemaAdapter) 추가 후 데이터셋의 SchemaAdapter 속성에 할당하면, 스키마어댑터 컴포넌트를 통해 여러 데이터셋의 데이터 변경을 순차적으로 처리할 수 있습니다.


스키마어댑터 컴포넌트는 Open, Close, SaveToStream, LoadFromStream, ApplyUpdates 등의 메소드를 제공합니다. 이 메소드를 호출하면 스키마어댑터 컴포넌트와 연결된 여러개의 데이터셋의 데이터를 열기/닫기, 저장/불러오기, 일괄적용 할 수 있습니다.


메모리 기반 데이터셋 컴포넌트(TFDMemTable)

메모리 상에서 데이터 처리를 지원합니다. 이번 실습의 클라이언트는 메모리 기반 데이터셋 컴포넌트에 서버에서 받은 데이터를 보관하고, 조작(추가, 수정, 삭제) 후 변경된 내용을 서버에 저장 요청합니다.


EMS FireDAC 클라이언트 컴포넌트(TEMSFireDACClient)

EMS 서버에 데이터셋 기반 REST End-point와 연동하는 컴포넌트입니다. EMS 프로바이더, 리소스, 스키마어댑터 속성을 제공합니다.

  • EMS 프로바이더 : 접속할 EMS 서버 정보 설정
  • 리소스 : 연결할 리소스 설정
  • 스키마어댑터 : 데이터를 처리 위임
GetDatas, PostUpdates 메소드를 제공합니다.
  • GetDatas : 서버에서 데이터를 요청합니다.
  • PostUpdates : 클라이언트에서 변경된 데이터를 서버에 저장 요청합니다.


[실습] 데이터셋 기반 REST API 개발하기

이 실습에서는 도서대여 프로그램의 도서정보와 사용자정보를 제공하는 REST API 서버를 개발하고, REST API 서버에서 받은 도서 정보를 편집 후 저장하는 클라이언트 프로그램 개발을 실습합니다.


REST API 자료 구조는 FireDAC 데이터셋의 JSON 포맷으로 저장 기능을 사용합니다.

데이터셋 기반 REST API 서버 개발하기

이 실습에서는 "도서대여 프로그램 만들기"의 도서(BOOK)와 사용자(USERS) 테이블 데이터를 제공하는 REST API 서버 개발을 실습합니다.


다음 순서로 진행합니다.

1) EMS 패키지 프로젝트를 생성합니다.

2) 데이터 제공하기 위해 컴포넌트 추가 및 설정합니다.

3) GET 메소드를 구현해 데이터 제공 기능을 개발합니다.

4) POST 메소드를 구현해 데이터 저장 기능을 개발합니다.


EMS 패키지 프로젝트 생성

EMS 패키지 프로젝트를 생성(File > New > Other > Delphi Projects > EMS > EMS Package) 합니다.


리소스 이름을 "datasets"로 지정합니다.



엔드포인트는 Get과 Post를 선택합니다.


프로젝트와 소스코드를 저장합니다.


데이터 제공을 위한 컴포넌트 추가 및 설정


아래 목록을 참고해 데이터 연결을 위한 컴포넌트 추가 및 설정합니다.

  • conBookRental: TFDConnection
    • 도서대여 프로그램 데이터베이스 연결
  • scmAdtBookRental: TFDSchemaAdapter
  • qryBook: TFDQuery
    • CachedUpdates = True
    • Connection = conBookRental
    • SQL = "SELECT BOOK_SEQ, BOOK_TITLE, BOOK_AUTHOR, BOOK_ISBN, BOOK_PRICE, BOOK_LINK FROM BOOK"
    • SchemaAdapter = scmAdtBookRental
    • UpdateOptions.AutoIncFields = BOOK_SEQ
  • qryUser: TFDQuery
    • CachedUpdates = True
    • Connection = conBookRental
    • SQL = "SELECT USER_SEQ, USER_NAME, USER_BIRTH, USER_SEX, USER_PHONE, USER_MAIL FROM USERS"
    • SchemaAdapter = scmAdtBookRental
    • UpdateOptions.AutoIncFields = USER_SEQ
  • FDStanStroageJSONLink1: TFDStanStorageJSONLink


엔드포인트 추가

기본으로 생성한 엔드포인트에는 전체 데이터를 제공, 저장하는 용도로 사용합니다.

"/books/" 엔드포인트를 추가해 도서 정보만 제공, 저장하는 과정도 소개합니다.(이 엔드포인트는 클라이언트 실습에서 사용하지 않습니다.)

published procedure Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse); procedure Post(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse); [ResourceSuffix('/books/')] procedure GetItemBooks(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse); [ResourceSuffix('/books/')] procedure PostBooks(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse); end;


Get / GetItemBooks

procedure TDatasetResource1.Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  Stream: TMemoryStream;
begin
  Stream := TMemoryStream.Create;
  try
    scmAdtBookRental.SaveToStream(Stream, TFDStorageFormat.sfJSON);

    AResponse.Body.SetStream(Stream, 'application/json', True);
  except
    Stream.DisposeOf;
  end;
end;

procedure TDatasetResource1.GetItemBooks(const AContext: TEndpointContext;
  const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  Stream: TMemoryStream;
begin
  Stream := TMemoryStream.Create;
  try
    qryBook.SaveToStream(Stream, TFDStorageFormat.sfJSON);

    AResponse.Body.SetStream(Stream, 'application/json', True);
  except
    Stream.DisposeOf;
  end;
end;

Get과 GetItemBooks 메소드는 데이터셋의 데이터를 스트림으로 JSON 포맷으로 저장 후 응답합니다.


Get은 스키마 어댑터와 연결된 데이터셋 들의 데이터(도서, 사용자)를 일괄 제공합니다.

GetItemBooks는 도서 쿼리(데이터셋)의 데이터만 제공합니다.


Post / PostBooks

procedure TDatasetResource1.Post(const AContext: TEndpointContext;
  const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  Stream: TStream;
begin
  if not SameText(ARequest.Body.ContentType, 'application/json') then
    AResponse.RaiseBadRequest('content type');
  if not ARequest.Body.TryGetStream(Stream) then
    AResponse.RaiseBadRequest('Invailed stream');

  Stream.Position := 0;
  scmAdtBookRental.LoadFromStream(Stream, TFDStorageFormat.sfJSON);
  scmAdtBookRental.ApplyUpdates;
end;

procedure TDatasetResource1.PostBooks(const AContext: TEndpointContext;
  const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  Stream: TStream;
begin
  if not SameText(ARequest.Body.ContentType, 'application/json') then
    AResponse.RaiseBadRequest('content type');
  if not ARequest.Body.TryGetStream(Stream) then
    AResponse.RaiseBadRequest('Invailed stream');

  Stream.Position := 0;
  qryBook.LoadFromStream(Stream, TFDStorageFormat.sfJSON);
  qryBook.ApplyUpdates;
end;

Post와 PostBooks 메소드는 요청 바디의 컨텐트타입을 확인하고, 바디의 컨텐트를 스트림으로 가져옵니다.(TryGetStream) 


Post 메소드는 스키마 어댑터로 데이터가 담긴 스트림의 데이터를 읽고, DB에 적용(ApplyUpdates)합니다.

PostBooks 메소드는 도서 쿼리에서 데이터가 담긴 스트림의 데이터를 읽고, DB에 적용(ApplyUpdates)합니다.


데이터셋 기반 REST API 클라이언트 개발하기

이 실습에서는 위에서 개발한 데이터셋 기반 REST API 서버와 연결해 데이터를 불러오고, 데이터 변경(추가, 수정, 삭제) 후 서버에 저장 요청합니다.


멀티-디바이스 애플리케이션(파이어몽키) 프로젝트로 진행합니다.(VCL 폼 애플리케이션에서도 같은 방법으로 개발할 수 있습니다.)


다음 순서로 진행합니다.

1) 프로젝트 생성 및 화면 개발

2) 데이터 연결 기능 개발

3) UI 컨트롤과 데이터 연결

4) 각 버튼 이벤트 개발


프로젝트 생성 및 화면 개발

멀티-디바이스 애플리케이션 프로젝트를 생성(File > New > Multi-Device Application)합니다.

(앞에서 만든 REST API 서버와 프로젝트 그룹으로 묶어 놓으면 편리합니다. Project Group > Add New Project)


아래 그림을 참고해 화면을 개발합니다.


에디트(TEdit)의 이름을 각각 "edtTitle, edtAuthor, edtISBN, edtPrice, edtLink"로 변경합니다.

버튼(TButton)의 이름을 각각 "btnSearch, btnDelete, btnAppend, btnSave, btnCancel"로 변경합니다.


데이터 연결 기능 구현

프로젝트에 데이터 모듈 추가 후 EMS 서버와 연결할 컴포넌트를 추가하겠습니다.


프로젝트에 데이터 모듈을 추가(File > New > Other > Delphi Files > Data Module)합니다.

데이터 모듈의 이름을 변경하고, 파일을 저장합니다.(저는 이름은 dmData, 파일이름은 DataAccessModule.pas로 했습니다.)


아래 그림과 표를 참고해 컴포넌트를 추가합니다.


 상위 오브젝트

 오브젝트

 속성

 값(또는 설명)

 dmData

 (데이터 모듈)

 EMSProvider1

 URLHost

 localhost(EMS 서버 주소)

 URLPort 8080

 FDSchemaAdapter1

 

 

 EMSFireDACClient1 Provider

 EMSProvider1

 Resource

 datasets

 SchemaAdapter FDSchemaAdapter1

 tblAdtBook

 (TFDTableAdapter)

 DatSTableName

 qryBook

 Name

 tblAdtBook

 SchemaAdapter FDSchemaAdapter1

 qryBook
 (TFDMemTable)

 Adapter

 tblAdtBook

 CachedUpdates True

 tblAdtUser

 (TFDTableAdapter)

 DatSTableName

 qryUser

 Name tblAdtUser
 SchemaAdapter FDSchemaAdapter1

 qryUser

 (TFDMemTable)

 Adapter

 tblAdtUser

 CachedUpdates

 True

 FDGUIxWaitCursor1

  


UI 컨트롤과 데이터 연결

라이브 바인딩 기술을 이용해 UI 컨트롤에 데이터를 표현합니다.


메모리 테이블에 필드 정보 추가하기

에디트(TEdit) 등의 UI 컨트롤과 데이터 연결 시 데이터셋의 필드 정보를 이용합니다. 메모리 테이블의 경우 자체적으로 필드 정보를 생성할 수 없어 아래 절차를 통해 메모리 테이블에 필드 정보를 추가합니다.


1) REST API 서버 프로젝트를 열고, 쿼리 컴포넌트(TFDQuery)의 Field Editor 메뉴를 선택합니다.


2) Field Editor 창의 팝업 메뉴에서 Add all fields 메뉴를 선택해 필드 정보를 추가합니다.


3) 추가된 필드 정보를 모두 선택 후 (클립보드로)복사 합니다.


4) 현재 프로젝트의 데이터 모듈로 돌아와 메모리 테이블 팝업 메뉴에서 Field Editor 메뉴를 선택합니다.


5) Field Editor 창에 붙여넣기 합니다.


위 과정을 메모리 테이블 별로 진행합니다.


라이브 바인딩으로 연결

폼 화면 유닛의 유즈(uses) 절에 데이터 모듈 유닛을 추가합니다.


폼 화면에서 라이브 바인딩 디자이너를 표시(View > Tool Windows > LiveBindings Designer)합니다.


아래 그림을 참고해 UI 컨트롤과 데이터를 연결(Linking)합니다.


버튼 기능 구현

각 버튼의 클릭 이벤트를 아래 코드를 참조해 입력합니다.

procedure TForm1.btnSearchClick(Sender: TObject);
begin
  dmData.EMSFireDACClient1.GetData;
end;

procedure TForm1.btnDeleteClick(Sender: TObject);
begin
  dmData.qryBook.Delete;
end;

procedure TForm1.btnAppendClick(Sender: TObject);
begin
  dmData.qryBook.Append;
end;

procedure TForm1.btnSaveClick(Sender: TObject);
begin
  dmData.EMSFireDACClient1.PostUpdates;
end;

procedure TForm1.btnCancelClick(Sender: TObject);
begin
  dmData.qryBook.Cancel;
end;

조회와 저장은 서버의 데이터를 다뤄야 하기 때문에 EMSFireDACClient를

추가, 삭제, 취소는 로컬의 메모리 테이블(qryBook)을 다루도록 구현했습니다.


테스트

REST API 서버와 클라이언트 구현이 완료되었습니다. 간단한(?) 작업으로 REST API 서버를 제작하고, 클라이언트에서 데이터 조회, 추가, 편집, 삭제 기능을 개발할 수 있었습니다.


테스트는 다음 단계로 진행합니다.

1) REST API 서버 실행(EMS 패키지 프로젝트를 실행합니다.)

2) REST API 클라이언트 실행

3) [조회] 버튼으로 데이터 출력

4) 데이터 추가, 수정 후 [저장] 버튼 클릭


참고/관련 자료