Search results for '분류 전체보기'

  1. 2020.08.31 -- [REST API] REST 기반 파일 업로드와 다운로드 구현하기
  2. 2020.07.15 -- OAuth 2.0 연동 - 네이버 API 연동(네이버 아이디로 로그인)
  3. 2020.02.05 -- 3회차 "커뮤니케이션 데이 - 마이그레이션" 회고
  4. 2020.01.22 -- 데브기어 컴포넌트 컨버터 소개(오픈소스)
  5. 2020.01.03 -- [오픈소스] TGPuttyLib 소개 - PuTTY 기반 SFTP 클라이언트
  6. 2019.12.12 -- 델파이에서 아이콘 폰트 사용하기
  7. 2019.12.05 -- 첫번째 "델파이 마이그레이션 DAY" 회고
  8. 2019.11.08 -- FCM 전송 구현하기 - 앱 서버 프로토콜 사용 메시지 전송
  9. 2019.10.28 -- 강동구청 특강
  10. 2019.10.25 -- FMX TListView Item 높이 조절 - 이미지가 없는 경우 낮게 표시하기
  11. 2019.10.10 -- 델파이 컴포넌트/소스 마이그레이션 자동화 도구
  12. 2019.10.02 -- 엔터프라이즈 커넥터로 VCL 애플리케이션에서 '구글 시트' 데이터 조회 및 편집하기
  13. 2019.09.19 -- [RAD서버] JSON 처리 단순화 컴포넌트 활용
  14. 2019.08.30 -- FireDAC 자동증가필드 적용 및 갱신
  15. 2019.06.20 -- [개발환경] 깃허브 PR을 이용한 코드리뷰 환경 구성(4)
  16. 2019.06.19 -- [개발환경] 깃허브에 저장소 생성 및 연동하기(3)
  17. 2019.06.19 -- [개발환경] RAD 스튜디오에서 Git 설정 및 불러오기(2)
  18. 2019.06.18 -- [개발환경] Git 설치와 저장소 구성(1)
  19. 2019.06.10 -- [FMX] RAD 스튜디오 10.3.1에서 FCM 전송 설정하기 (2)
  20. 2019.05.30 -- 델파이 코드 주석 추가 및 문서화 방안

[REST API] REST 기반 파일 업로드와 다운로드 구현하기

2020. 8. 31. 15:19

이 글에서는 REST API 기반 파일 업로드와 다운로드 구현방안을 설명합니다.

REST 서버와 REST 클라이언트를 이용해 기능을 구현했습니다.


REST 기반 파일 업로드와 다운로드 구현


REST API 구현 시 파일을 제공해야하는 경우가 있습니다. 파일 업로드 시 기존의 데이터와 함께 파일을 업로드할 수도 있고, 별도의 파일 전용 엔드포인트를 추가해 구현할 수 있습니다. 이 두가지 방법 모두에 대해 설명합니다.


이 글에 앞서 다음 내용을 이해하고 있어야 합니다. 미리 선행 학습이 필요합니다.


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

  • 파일 엔드포인트 추가 구성
  • 파일 업로드 구현 방안
    • 서버 측 구현
    • 클라이언트 측 구현
  • 파일 다운로드 구현 방안
    • 서버 측 구현
    • 클라이언트 측 구현


파일 엔드포인트 추가 구성

파일을 제공하는 기능을 추가하기 위해서는, 1) 기존 엔드포인트에서 파일 항목을 추가하는 방법과 2) 별도의 엔드포인트를 추가하는 방법으로 구현할 수 있습니다.


이 글에서는 별도의 엔드포인트를 추가해 파일 업로드와 다운로드 기능을 구현하는 방법을 설명합니다. 


저는 images라는 리소스이름으로 RAD 서버 패키지 프로젝트를 생성했습니다.

1, File > New > Other

2, RAD Server > RAD Server Package

3, Create package with resource > Next

4, Resource name: images, File type: Data Module > Next

5, 모든 항목 선택 해제 > Finish


다음과 같이 엔드포인트를 추가합니다.(선언부에 추가 후 Ctrl + Shift + C를 눌러 구현부를 자동 생성할 수 있습니다.)

type
  [ResourceName('images')]
  TImagesResource1 = class(TDataModule)
  published
    [ResourceSuffix('{item}/photo')]
    procedure GetItemPhoto(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    [ResourceSuffix('{item}/photo')]
    procedure PostItemPhoto(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
  end;

엔드포인트는 ResourceSuffix(리소스 접미사)와 메소드로 구성됩니다.


메소드는 Get, Post, Put, Delete 접두사로 시작해야 합니다. 리소스 호출 시 HTTP 메소드와 접두사가 매핑되어 메소드가 호출 됩니다.

즉, GET으로 요청된 경우 Get*으로 시작된 메소드가, POST로 요청한 경우 Post*로 시작된 메소드가 실행됩니다.


ResourceSuffix에 지정된 항목과 매핑된 요청이 온 경우 메소드가 실행됩니다.

중괄호({})로 감싼 부분은 파라미터화된 항목으로 구현부에서 ARequest.Params.Values['item'] 등의 코드로 그부분의 내용을 취득할 수 있습니다.


예를 들어

GET http://localhost:8080/images/1/photo 호출 시 GetItemPhoto 메소드가 호출되고

POST http://localhost:8080/images/1/photo 호출 시 PostItemPhoto 메소드가 호출됩니다.

ARequest.Params.Values['item']은 1을 반환합니다.


파일 업로드 구현 방안

서버(RAS Server) 측 구현

파일 업로드는 PostItemPhoto 메소드에서 구현합니다.


주의할 점은, 파일 전송을 위해서는 multipart/form-data 인코딩 타입으로 데이터가 전달됩니다.

현재(2020년 08월)에 RAD 서버에서 multipart/form-data 타입의 데이터를 처리하는 기능이 구현되어있지 않은 것으로 파악되어, 직접 데이터를 분석해 필요한 데이터를 사용해야 합니다.


저는 다음과 같은 DecodeParamsAndStream 함수를 이용해 데이터를 분석했습니다.

(Indy에서 재공하는 TIdMessageDecoderMIME 객체를 이용했습니다.)

type
  TStreamParams = class(TDictionary)
  private
    function GetStream(const Name: string): TStream;
    procedure SetStream(const Name: string; const Value: TStream);
  public
    property Streams[const Name: string]: TStream read GetStream write SetStream;
    destructor Destroy; override;
  end;

procedure DecodeParamsAndStream(AStream: TStream; AContentType: string;
  Params: TStrings; StreamParams: TStreamParams);

파라메터로는 

  • AStream : Body의 전체 스트림
  • AContentType : 요청의 컨텐트 타입, boundary 포함
  • Params : 문자열 형식의 데이터(파라미터)
  • StreamParams : Stream 형식의 데이터(파라미터), TStreamParams 객체는 <string, TStream> 쌍의 딕셔너리 사용

컨텐트 타입은 다음과 같이 데이터의 인코딩 타입과 boundary값이 포함됩니다.
multipart/form-data; boundary=---------Embt-Boundary--493D921E3683D69B

REST Client에서 파일 업로드 요청한 데이터(Body의 스트림)의 내용은 아래 그림과 같습니다.


파라미터 데이터들은 컨텐트타입의 boundary 값을 앞뒤에 두어 구분합니다.

주의할 점은 문자열 형식의 파라미터 데이터의 경우 Content-Type이 누락되어 있습니다.(분석 시 누락된 경우에 대해 예외처리가 필요합니다.)


위 데이터를 분석한 내용은 다음과 같습니다.
uses
  System.IOUtils,
  IdGlobalProtocols, IdMessageCoder, IdMessageCoderMIME;

procedure DecodeParamsAndStream(AStream: TStream; AContentType: string;
  Params: TStrings; StreamParams: TStreamParams);
var
  Boundary: string;
  Decoder, NewDecoder: TIdMessageDecoderMIME;
  MsgEnd: Boolean;

  FieldName: string;
  StringStream: TStringStream;
  MemoryStream: TMemoryStream;
begin
  Boundary := ExtractHeaderSubItem(AContentType, 'boundary', QuoteHTTP);

  Decoder := TIdMessageDecoderMIME.Create(nil);
  try
    MsgEnd := False;
    repeat
      Decoder.MIMEBoundary := Boundary;
      Decoder.SourceStream := AStream;
      Decoder.FreeSourceStream := False;

      Decoder.ReadHeader;
      { Content-Type이 없는 경우 mcptAttachment로 인식해 기본값 설정
        RESTClient에서 MultiPart 전송 시 일반 파라메터의 경우 Content-Type 누락해 전송 함}
      if Decoder.Headers.Values['Content-Type'] = '' then
      begin
        Decoder.Headers.Values['Content-Type'] := 'text/plain';
        Decoder.CheckAndSetType(Decoder.Headers.Values['Content-Type'], Decoder.Headers.Values['Content-Disposition']);
      end;
      case Decoder.PartType of
        mcptText:
          begin
            FieldName := ExtractHeaderSubItem(Decoder.Headers.Values['Content-Disposition'], 'name', QuoteMIME);
            StringStream := TStringStream.Create;
            try
              NewDecoder := Decoder.ReadBody(StringStream, MsgEnd) as TIdMessageDecoderMIME;
              try
                Params.Values[FieldName] := StringStream.DataString.Trim;
              finally
                Decoder.Free;
                Decoder := NewDecoder;
              end;
            finally
              StringStream.Free;
            end;
          end;
        mcptAttachment:
          begin
            var HL: string := Decoder.Headers.Values['Content-Disposition'];
            FieldName := ExtractHeaderSubItem(Decoder.Headers.Values['Content-Disposition'], 'name', QuoteMIME);
            MemoryStream := TMemoryStream.Create;
            NewDecoder := Decoder.ReadBody(MemoryStream, MsgEnd) as TIdMessageDecoderMIME;
            try
              StreamParams.Streams[FieldName] := MemoryStream;
            finally
              Decoder.Free;
              Decoder := NewDecoder;
            end;
          end;
        mcptIgnore:
          begin
            FreeAndNil(Decoder);
            Decoder := TIdMessageDecoderMIME.Create(nil);
            TIdMessageDecoderMIME(Decoder).MIMEBoundary := Boundary;
          end;
        mcptEOF:
          begin
            FreeAndNil(Decoder);
            MsgEnd := True;
          end;
      end;

    until (Decoder = nil) or MsgEnd;
  finally
    Decoder.Free;
  end;
end;


위의 코드가 좀 길고 생소할 수 있습니다. 

중요한 부분은 디코더로 분석한 데이터의 Decoder.PartType이 mcptText(텍스트)인 경우 Params 항목에 데이터를 추가하고, mcptAttachment(첨부 파일)인 경우 StreamParams 항목에 데이터를 추가했습니다.


위 함수를 사용한 PostItemPhoto 메소드의 코드는 다음과 같습니다.

const
  ROOT_PATH = 'D:\Projects\DelphiDemos\OpenAPI\RESTUpload\Server';

procedure TImagesResource1.PostItemPhoto(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  LItem: string;

  Stream: TStream;
  ContentType: string;

  Params: TStringList;
  StreamParams: TStreamParams;
  Path, RawPath: string;
begin
  LItem := ARequest.Params.Values['item'];

  Params := TStringList.Create;
  StreamParams := TStreamParams.Create;

  Stream := ARequest.Body.GetStream;
  ContentType := ARequest.Headers.GetValue('Content-Type');
    // e.g. multipart/form-data; boundary=--------070120105641002

  Path := TPath.Combine(ROOT_PATH, 'images');
  RawPath := TPath.Combine(Path, 'raw_' + LItem + '.jpg');
  Path := TPath.Combine(Path, LItem + '.jpg');

  // Save raw data
  TMemoryStream(Stream).SaveToFile(RawPath);

  // Decode parameters and streams from body stream.
  DecodeParamsAndStream(Stream, ContentType, Params, StreamParams);

  // Using parameters as a below
  if Params.Values['user_id'] = '123' then
  begin
  end;

  // Save photo parameter to a file.
  if Assigned(StreamParams.Streams['photo']) then
    TMemoryStream(StreamParams.Streams['photo']).SaveToFile(Path);

  StreamParams.Free;
  Params.Free;
end;

참고로, 파일 저장 방식은 파일로 저장하는 방식과 Blob 필드에 저장하는 방식이 있으며, 이 예제에서는 지정경로(ROOT_PATH) 하위 images 디렉토리에 {item}항목 이름으로 저장했습니다.


파일로 저장시의 주의점은 RAD 서버의 로컬 디스크로 저장 시 서버를 병렬화(여러대 구성) 시 접근이 제한됩니다. 네트워크 경로 또는 공유 파일 시스템을 이용해야 합니다.(또는 파일 공유 솔루션 등을 이용할 수도 있습니다.)


Blob 필드로 저장 시 주의점은 데이터와 별도의 테이블에 Blob 필드를 구성하는 것을 추천드립니다. 성능 측면과 향후 데이터 관리(백업 등) 시 유리할 수 있습니다.


클라이언트(REST Client) 측 구현

REST 클라이언트를 이용해 파일 업로드 구현은 데이터 전송과 크게 차이나지 않습니다.

procedure TForm1.Button1Click(Sender: TObject);
var
  Filepath: string;
  Stream: TFileStream;
begin
  if not OpenDialog1.Execute then
    Exit;

  Filepath := OpenDialog1.FileName;
  Stream := TFileStream.Create(Filepath, fmOpenRead);

  RESTClient1.BaseURL := 'http://localhost:8080';
  RESTRequest2.Method := rmPOST;
  RESTRequest2.Resource := 'images/{item}/photo';
  RESTRequest2.Params.ParameterByName('item').Value := Edit1.Text;

  RESTRequest2.Params.AddItem('user_id', '123');
  RESTRequest2.Params.AddItem('photo', Stream, pkFILE, [], ctAPPLICATION_OCTET_STREAM);
  RESTRequest2.Execute;

  Stream.Free;
end;

파일을 추가할 경우, TStream 이용하므로, TStream을 상속받는 객체들(TFileStream, TMemoryStream 등)을 이용할 수 있습니다.


스트림을 파라미터로 추가하는 코드는 다음과 같습니다.

RESTRequest2.Params.AddItem('photo', Stream, pkFILE, [], ctAPPLICATION_OCTET_STREAM);


파라미터 종류를 pkFILE로 지정시 내부적으로 multipart/form-data 인코딩 타입으로 데이터가 전송되며, 컨텐트타입을 ctAPPLICATION_OCTET_STREAM으로 지정해야 합니다.


파일 다운로드 구현 방안

서버(RAD Server) 측 구현

파일 다운로드는 GetItemPhoto 메소드에서 구현합니다.
procedure TImagesResource1.GetItemPhoto(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  LItem: string;

  Path: string;
  Stream: TStream;
begin
  LItem := ARequest.Params.Values['item'];

  Path := TPath.Combine(ROOT_PATH, 'images');
  Path := TPath.Combine(Path, LItem + '.jpg');

  if not TFile.Exists(Path) then
    AResponse.RaiseNotFound('Not found', '''' + LItem + ''' is not found');

  Stream := TFileStream.Create(Path, fmOpenRead);
  AResponse.Body.SetStream(Stream, 'image/jpeg', True);
end;
요청한 {item} 항목의 파일을 스트림(TFileStream)으로 읽어 그대로 추력합니다.
주의할 점은 마지막 줄의 SetStream의 마지막 파라미터(AOwnerValue)를 True로 지정해야 Stream 객체가 해제됩니다.(직접 해제 시 메모리 참조 오류가 발생합니다.) False로 지정 시 객체를 복사하므로, 직접 해제해도 됩니다.

클라이언트(REST Client) 측 구현

VCL Form Application에서 REST Client로 이미지를 다운로드 후 이미지(TImage)에 표시하는 코드는 다음과 같습니다.
procedure TForm1.Button2Click(Sender: TObject);
var
  WICImage: TWICImage;
  Stream: TMemoryStream;
begin
  RESTClient1.BaseURL := 'http://localhost:8080';
  RESTRequest1.Method := rmGET;
  RESTRequest1.Resource := 'images/{item}/photo';
  RESTRequest1.Params.ParameterByName('item').Value := Edit1.Text;
  RESTRequest1.ExecuteAsync(procedure
    begin
      if RESTResponse1.StatusCode = 404 then
        Exit;
      Stream := TMemoryStream.Create;
      Stream.WriteData(RESTResponse1.RawBytes, RESTResponse1.ContentLength);
      WICImage := TWICImage.Create;
      WICImage.LoadFromStream(Stream);
      Image1.Picture.Assign(WICImage);
      WICImage.Free;
      Stream.Free;
    end);
end;
이미지 등의 용량이 큰 요청의 경우 비동기로 요청하는 것이 좋습니다.
다양한 이미지 포맷을 지원하기 위해 TWICImage 컴포넌트를 사용했습니다.

완성된 프로젝트 샘플코드


험프리 험프리.김현수 Delphi/C++Builder RAD Server, REST, Rest Client

OAuth 2.0 연동 - 네이버 API 연동(네이버 아이디로 로그인)

2020. 7. 15. 11:30

이 글에서는 OAuth 2.0을 이용 네이버 API와 연동하는 방법을 알아봅니다.

이 글에서는 네이버 아이디로 로그인 후 회원 프로필 조회 API와 연동해 프로필 정보를 불러오는 델파이 애플리케이션을 작성할 수 있습니다.


다음 글을 통해 OAuth 2.0을 이용 카카오 API와 연동하는 내용을 다뤘습니다.


OAuth2.0 연동하는 절차는 카카오, 네이버 API 뿐아니라 대부분의 서비스들이 비슷합니다.

이 글에서는 카카오 API와 네이버 API의 차이점에 대해서만 간략히 설명합니다. 이글을 읽기 전 위 링크의 내용을 먼저 숙지하시기 바랍니다.

(네이버와 카카오 OAuth 2.0의 차이점은 앱 등록하는 과정, API의 엔드포인트(URI)와 일부 파라미터 종류 및 이름 뿐입니다.)


[준비] 준비사항, 앱 등록

네이버 아이디로 로그인 API의 명세는 다음 링크로 확인할 수 있습니다.
사전 준비사항으로 다음 링크에서 애플리케이션을 등록해야 합니다.

등록 후 애플리케이션 정보에서 Client ID와 Client Secret을 확인합니다.(구현 시 사용)


API 설정에서 "사용 API" 항목에서 "네아로(네이버 아이디로 로그인)"항목을 선택하고, 제공 정보를 선택합니다.


API 설정에서 "로그인 오픈 API 서비스 환경" 항목에서 "PC웹" 환경을 추가하고, 서비스 URL과 Callback URL을 등록합니다.(테스트 시에는 제 OAuth 페이지를 이용해도 좋습니다. http://hjf.pe.kr/oauth)


네이버 아이디로 로그인 시 사용하는 API는 다음과 같습니다.

[1단계] 로그인 및 인증코드 취득

"네이버 아이디로 로그인 인증 요청" 페이지의 요청 변수는 다음과 같습니다.


대부분 카카오 API와 같지만, 새로운 state 값이 있습니다. 해당 값은 사이트 간 요청 위조 공격을 방지하기 위한 값으로, 요청 시 입의의 값을 전달하면 응답시 동일한 값을 반환해 내가 작성한 요청에 대한 응답인지를 확인하기 위한 목적입니다. state는 임의의 값을 사용해도 됩니다. 샘플에서는 "abcdef" 값을 사용했습니다.(랜덤으로 문자열을 생성해서 사용해도 됩니다.)


작성된 코드는 다음과 같습니다.

procedure TForm3.Button1Click(Sender: TObject);
var
  URL, Param: string;
begin
  URL := 'https://nid.naver.com';
  Param := '/oauth2.0/authorize?response_type=code&client_id={client_id}&redirect_uri={redirect_uri}&state={state}';
  Param := Param.Replace('{client_id}', CLIENT_ID);
  Param := Param.Replace('{redirect_uri}', REDIRECT_URI);
  Param := Param.Replace('{state}', 'abcdef');

  WebBrowser1.URL := URL + Param;
  WebBrowser1.Navigate;
end;

위 코드를 이용해 실행 후 로그인 및 동의 화면 후 다음과 같은 웹페이지로 리다이렉션 됩니다.
  • http://hjf.pe.kr/oauth/?code=7geMnuaVmQDsDbpoUl&state=abcdef
이동된 페이지 URL에서 code 정보를 취득 후 엑세스 토큰 취득시 사용합니다.

[2단계] 엑세스 토큰 취득

엑세스 토큰 취득 API의 요청 변수는 다음과 같습니다.

1단계에서 취득한 code를 포함해 요청합니다.


작성된 코드는 다음과 같습니다.

var
  token: string;
begin
  RESTClient1.BaseURL := 'https://nid.naver.com/';

  RESTRequest1.Resource := 'oauth2.0/token';
  RESTRequest1.Method := rmPOST;
  RESTRequest1.Params.Clear;
  RESTRequest1.Params.AddItem('grant_type',     'authorization_code');
  RESTRequest1.Params.AddItem('client_id',      CLIENT_ID);
  RESTRequest1.Params.AddItem('client_secret',  CLIENT_SECRET);
  RESTRequest1.Params.AddItem('code',           Edit1.Text);
  RESTRequest1.Params.AddItem('state',          'abcdef');

  RESTRequest1.Execute;

  Memo1.Lines.Text := RESTResponse1.Content;

  if RESTResponse1.JSONValue.TryGetValue<string>('access_token', token) then
    Edit2.Text := token;
end;

다음과 같은 응답을 받으면, 필요한 access_token을 가져옵니다.

[3단계] 서비스에 접근(엑세스 토큰 이용)

엑세스 토큰으로 다양한 네이버 API에 접근할 수 있습니다.(단, 애플리케이션 등록 시 설정한 앱에 한함)


네이버 회원 프로필 조회 API는 다음과 같습니다.


정상적인 요청에 대한 응답은 다음과 같습니다.


작성된 코드는 다음과 같습니다.

procedure TForm3.Button3Click(Sender: TObject);
var
  nickname, name, profile_url: string;
  Stream: TMemoryStream;
begin
  OAuth2Authenticator1.AccessToken := Edit2.Text;

  RESTClient2.BaseURL := 'https://openapi.naver.com/';
  RESTClient2.Authenticator := OAuth2Authenticator1;

  RESTRequest2.Client := RESTClient2;
  RESTRequest2.Response := RESTResponse2;

  RESTRequest2.Resource := 'v1/nid/me';
  RESTRequest2.Execute;

  Memo1.Lines.Text := RESTResponse2.Content;

  name := RESTResponse2.JSONValue.GetValue<string>('response.name');
  nickname := RESTResponse2.JSONValue.GetValue<string>('response.nickname');
  Edit3.Text := name;
  Edit4.Text := nickname;

  profile_url := RESTResponse2.JSONValue.GetValue<string>('response.profile_image');

  RESTClient3.BaseURL := profile_url;
  RESTRequest3.Execute;
  RESTRequest3.ExecuteAsync(procedure
    begin
      Stream := TMemoryStream.Create;
      try
        Stream.WriteData(RESTResponse3.RawBytes, RESTResponse3.ContentLength);
        ImageControl1.Bitmap.LoadFromStream(Stream);
      finally
        Stream.Free;
      end;
    end
  );
end;

완료 및 테스트

완성된 화면의 테스트 결과는 다음과 같습니다.


완성된 프로젝트 파일

NaverOAuth20.zip


추가 학습할 내용

이 글은 다음 링크의 OAuth2.0 카카오 API 연동하는 내용과의 차이점을 기술합니다. OAuth 2.0의 프로세스와 카카오 API 연동의 자세한 내용은 다음 링크에서 확인할 수 있습니다.

네이버, 카카오 등의 서비스 인증(OAuth 2.0) 및 API 연동은 REST API 기반으로 구현됩니다. 다음 글들을 통해 REST API를 이해하고 실습할 수 있습니다.

관련/참고 링크


험프리 험프리.김현수 파이어몽키

3회차 "커뮤니케이션 데이 - 마이그레이션" 회고

2020. 2. 5. 11:46

데브기어에서는 매월 첫번째 화요일 커뮤니케이션 데이를 진행합니다.


델파이/C++빌더 커뮤니케이션 데이는? 

커뮤니케이션 데이는 특정 주제로 델파이/C++빌더 개발자 분들이 모여 함께 토의하고, 정보를 공유하고, 전문가의 조언을 들을 수 있는 오프라인 모임입니다. 매월 첫째주 화요일 데브기어 라운지에서 진행됩니다. 
다음 신청 페이지에서 신청할 수 있습니다.


2월은 마이그레이션 주제로 진행되었습니다.


다음 참석대상자 분들은 많은 도움을 받을 수 있을것입니다.

  • 마이그레이션을 계획 중이며, 효과적인 마이그레이션 프로세스를 조언받고 싶은 분들
  • 마이그레이션 중 자체적으로 해결하기 어려운 이슈에 대한 해결 방향에 대한 조언
  • 써드파티 컴포넌트 대체 및 컴포넌트 통합에 대한 구체적인 방안
  • 마이그레이션 방향에 대한 전반적인 의견을 듣고 싶은 분들
  • 마이그레이션 프로젝트/컨설팅의 경험 및 노하우가 듣고 싶은 분들
  • 외부 사용자 증가 등으로 아키텍처 변경(예> 2티어 > 멀티티어)이 필요하신 분들
  • 기타 주제와는 상관 없는 개발 이슈에 대해 이야기하고 싶은 분들


3회차 마이그레이션 데이

어제(2.4)는 3회차 마이그레이션 데이가 진행되었고, 신청자 4분 중 2분이 참석해 주셨고, 순차적으로 방문하셔서 개별 면담 형식으로 진행했습니다.


미리 준비한 질문지에 현장 질문을 기록하고 답변 및 참고할 내용을 기록해 참석하신 분들에게 제공했습니다.


다음 진행 시에는 좀더 구체적인 질문을 받아서 미리 답변을 준비하도록 개선하는 것이 좋을 것 같습니다.


어제 진행된 주요 문의 내용은 다음과 같습니다.

  • 가스제어 장보 모니터링 소프트웨어 개발사
    • 프로젝트 방향과 개발방향 문의 목적으로 참가
    • C++빌더로 개발된 프로젝트를 델파이로 전환하는 방안 문의
    • 외주업체에서 받은 C++빌더 프로젝트를 내무 개발자가 유지보수하기 어렵다 판단해 변경을 원함
    • 상담결과, 굳이 델파이로 전환할 필요 없다 결정(C++ 학습 및 교육 진행)
    • 기타, DBMS 변경 및 연결 기술등의 개발 방향에 대해 안내
  • 반도체 유통 ERP 자체 개발 및 운영
    • 자체적으로 마이그레이션 진행이 필요하며, 마이그레이션 이슈 해결 및 방향 문의목적으로 참가
    • 써드파티 컴포넌트(리얼그리드, NumberEdit, FlatControl) 사용 중
    • 리얼그리드 전환의 어려움 > 기존 컨설팅 사례를 통해 컴포넌트 전환 자동화 방안 안내
    • 데브기어 마이그레이션 컨설팅 및 워크샵 과정 안내


많은 분들이 참석하지 않아 조촐히 진행했습니다. 하지만 참석자 분들의 적극적인 질문으로 오후시간을 모두 사용했습니다. 덕분에 현업의 많은 이야기를 들을 수 있는 의미있는 시간이었습니다. 참석자 분들도 의미있는 시간이었기를 바랍니다. 


다음 4회차 커뮤니케이션 데이에도 "마이그레이션" 주제로 새로운 분들을 만나뵙고 많은 이야기를 나누고 싶습니다. 메인 주제는 마이그레이션이지만 다른 기술적인 내용이나 평상시 잘 풀리지 않거나 궁금한 기술적인 내용들, 방향등을 이야기하고 싶다면 다음 링크에서 3월 커뮤니티 데이 참석을 신청해 주세요.



험프리 험프리.김현수 마이그레이션

데브기어 컴포넌트 컨버터 소개(오픈소스)

2020. 1. 22. 17:06

데브기어 컴포넌트 컨버터



데브기어 컴포넌트 컨버터는 델파이 소스파일을 분석해 컴포넌트와 소스코드를 변경해주는 오픈소스 기반 마이그레이션 도구입니다.


데브기어 컴포넌트 컨버터는 컴포넌트 컨버터와 소스코드 컨버터 두개의 애플리케이션으로 구성됩니다.

  • 컴포넌트 컨버터는 델파이 폼파일(*.dfm)과 소스파일(*.pas)에서 컴포넌트 정보를 변경합니다.
  • 소스코드 컴버터는 델파이 소스파일(*.pas)에서 컴포넌트를 사용한 코드를 찾아 변환합니다.

데브기어 컴포넌트 컨버터의 특징

엡바카데로는 reFind라는 정규표현식 치환 도구를 통해 마이그레이션 자동화 작업을 지원합니다.
reFind는 정규표현식을 이용해 소스파일에서 컴포넌트 종류와 속성등을 전환하는 작업을 할 수 있습니다.
하지만, reFind는 컴포넌트와 속성 등을 일대일로 치환하기 때문에 하나의 컴포넌트를 여러개의 컴포넌트로 나누거나, 속성, 이벤트 등을 추가하는 작업에 제약이 있습니다.

데브기어 컴포넌트 컨버터는 컴포넌트 코드를 분석해 컴포넌트 정보를 취득 후, 그 정보로 새로운 컴포넌트로 조합하는 방식으로 구현되었습니다.
이 방식을 이용해 하나의 컴포넌트를 여러개의 컴포넌트로 나누거나, 여러개의 컴포넌트 정보로 하나의 컴포넌트로 합치는 등의 작업이 가능합니다.

컴포넌트 정보를 분석하거나 조합하는 과정은 컨버터 클래스에 구현합니다.
이 컨버터 클래스는 컴포넌트 별로 작성해야 하며, 본인의 코딩 스타일에 따라 다르게 구현해야 합니다.
데브기어 컴포넌트 컨버터는 오픈소스로 제공하며, 소스코드를 다운로드 받아 본인의 소스코드에 맞게 직접 제작하는 과정이 필요합니다.

데브기어 컴포넌트 컨버터의 기능

데브기어 컴포넌트 컨버터의 주요 기능은 다음과 같습니다.
  • 소스파일과 폼파일에서 컴포넌트 정보를 변경합니다.
  • 소스파일에서 컴포넌트 사용 코드를 변경합니다.
  • 지정한 디렉토리 하위 파일을 선택해 일괄 변환 가능합니다.
  • 변환작업은 컨버터에 구현되며, 직접 컨버터를 구현해 변환 작업을 추가할 수 있습니다.
세부적인 특징은 다음과 같습니다.
  • 하나의 컴포넌트를 여러개의 컴포넌트로 나누어 변경할 수 있습니다.(예> TRealGrid -> TcxGrid, TcxLevel, TcxTableView)
  • 하나의 속성을 여러개의 속성으로 나누어 변경할 수 있습니다.
  • 이벤트 핸들러와 매개변수를 변경할 수 있습니다.
  • uses 절의 유닛을 추가, 제거할 수 있습니다.
  • 컴포넌트를 제거할 수 있습니다.
  • 컴포넌트 속성을 추가할 수 있습니다.

위 기능들을 이용해 대규모의 컴포넌트와 컴포넌트를 사용하는 소스코드를 일괄 변경할 수 있습니다.

제작 계기

이 도구를 제작한 계기는 리얼그리드(TRealGrid)를 퀀텀그리도(TcxGrid)로 전환하는 작업을 진행하기 위해서입니다.


엠바카데로에서는 이미 reFind라는 마이그레이션 도구를 제공합니다. reFind로 컴포넌트 또는 속성을 변경할 수 있습니다. 하지만 reFind는 일대일로 컴포넌트와 속성을 변경합니다.

리얼그리드를 퀀텀그리드로 변경하기 위해서는 하나의 리얼그리드 컴포넌트를 여러개의 퀀텀그리드 컴포넌트로 나눠야하고, 두 컴포넌트의 속성이 상이해 재구성해야 했습니다. 또한 이벤트 핸들러의 구조도 상이해 이벤트 핸들러 구조를 변경해 다시 연결해야 합니다. 이와 같이 컴포넌트와 구조가 변경되면 소스코드도 대폭 변경되어야 합니다.

만약, 컴포넌트를 사용한 코드가 몇군데라면 한명이 수작업으로 변경하는 것이 좋습니다.
하지만 사용하는 코드가 수십~수백개라면 여려명이 오랜시간 변경작업을 진행해야 할 것입니다. 이 경우 시간도 오래걸리고, 각자의 개발 스타일에 따라 다르게 구현하게 되면 향후 유지보수도 어려울 수 있습니다.

위 작업을 자동화 하면, 시간과 인력이 절약되고 동일한 방향으로 변경되어 유지보수에도 유리합니다. 또한 재작업에 대한 부담도 줄게됩니다.

사용방법

데브기어 컴포넌트 컨버터는 오픈소스로 제공됩니다.


이 도구는 완성된 패키지형 프로그램이 아닙니다. 변환작업을 수행할 컨버터를 직접 구현해야합니다.

다음 링크를 통해 데브기어 컴포넌트 컨버터의 구조와 구현방법을 설명합니다.

  • 데브기어 컴포넌트 컨버터 구조와 원리 - 준비 중
  • 데브기어 컴포넌트 컨버터 사용 샘플 - 준비 중

사용하며 필요한 기능이나 궁금한 내용은 의견 부탁드립니다.


험프리 험프리.김현수 마이그레이션

[오픈소스] TGPuttyLib 소개 - PuTTY 기반 SFTP 클라이언트

2020. 1. 3. 10:07

오픈소스 기반 SFTP 클라이언트 라이브러리를 소개합니다.

TGPuttyLib

TGPuttyLib는 독일 델파이 개발자 Tobias Giesen이 운영하는 오픈소스로, PuTTY 기반 SFTP 클라이언트 라이브러리를 제공합니다. 주요 특징으로 알려진 다른 라이브러리 보다 높은 전송속도를 제공한다고 합니다.




설명에 따르면 다음의 특징이 있습니다.

  • PuTTY 제품군에서 psftp 프로그램을 DLL로 변환한 것
  • 개발자는 가장 높은 전송 속도(100 MB/Sec 이상)로 파일 전송 가능(알려진 다른 라이브러리 보다 높은 속도)
  • C++, Delphi, Free Pascal 에서 즉시 사용 가능한 클래스 제공
  • PuTTY Release 0.73 기반
  • 2020년 1분기 MacOS 및 리눅스 지원 계획

컴파일된 데모와 자세한 내용은 프로젝트 웹사이트를 통해 확인할 수 있습니다.


컴파일된 데모


컴파일된 데모(DelphiVCLDemo)를 실행해본 결과, SFTP의 기본기능이 전반적으로 구현되어 완성도 높아 보였습니다.

데모의 코드는 클래스 기반으로 라이브러리를 다룹니다. 다양한 데모 코드를 제공해 손쉽게 원하는 기능 구현 가능할 것으로 평가됩니다.


개발자 웹사이트를 확인하니, 파일 동기화 및 백업 소프트웨어 솔루션 개발이 주업으로 보이며, 그 중 일부를 오픈소스로 공개한 것으로 보입니다.


기존에 개발한 FTP 작업의 속도가 느리거나, 더 빠른 속도로 FTP 작업이 필요한 경우 또하나의 선택지가 될 수 있을 것 같습니다.





험프리 험프리.김현수 Delphi/C++Builder

델파이에서 아이콘 폰트 사용하기

2019. 12. 12. 16:08

아이콘 폰트는 폰트파일에 문자 대신 아이콘을 추가해 아이콘을 사용할 수 있는 폰트파일입니다.


아이콘 폰트를 사용하면 다양한 아이콘을 손쉽게 그리고 통일되게 사용할 수 있습니다.


대표적인 아이콘 폰트는 다음과 같습니다.


위 링크에서 아이콘 폰트 설치 후 문자표(Characters map) 프로그램등으로 다음과 같이 글꼴을 확인할 수 있습니다. 문자 선택 시 하단에 코드(U+F087)가 표시됩니다.



이 글에서는 아이콘 폰트를 델파이에서 사용할 수 있도록하는 오픈소스들 소개합니다.

(소개하는 3가지 방식 모두 VCL 기반으로만 동작합니다.)

  • IconFontsImageList
  • FontIconEditor
  • Symbols

IconFontsImageList

IconFontsImageList는 TImageList 컴포넌트를 상속받은 TIconFontsImageList 컴포넌트를 이용해 아이콘 폰트를 사용할 수 있습니다.


위 링크에서 컴포넌트 다운로드 후 설치(라이브러리 패스 추가 필요) 후에 사용할 수 있습니다.


TIconFontsImageList 컴포넌트 추가 후 컴포넌트를 더블클릭하면 다음과 같은 에디터가 표시됩니다.


Properties of ImageList에서 사용할 아이콘 폰트(FontName)와 이미지 크기(Size), 색상(FontColor) 등을 선택합니다.

Properties of Selected Icon에서 [Add] 버튼을 눌러 이미지를 추가합니다.

폰트 아이콘 코드를 입력합니다.(문자표 등에서 확인 가능: [Show Char Map...] 버튼 이용)


이후 기존 이미지리스트와 동일하게 사용할 수 있습니다.


FontIconEditor

FontIconEditor 프로젝트는 기존 이미지 리스트(TImageList)에 아이콘 폰트 추가하는 기능을 확장하는 컴포넌트입니다.


해당 컴포넌트를 설치하면 이미지리스트 팝업 메뉴에 "Add font icons..." 메뉴가 추가됩니다.

해당 메뉴 클릭 시 아래와 같이 등록화면이 표시됩니다.


아이콘 폰트의 아이콘을 선택 해 이미지 리스트에 추가할 수 있습니다.


Symbols

한국 델파이 구루이신 안영제 님께서 공개한 내용입니다. 샘플 프로젝트입니다.


Segoe MDL2 Assets 폰트를 이용했지만, 다른 아이콘 폰트를 사용해도 됩니다.

(단, 개발PC 뿐 아니라, 사용자PC에도 해당 폰트가 설치되어 있어야 합니다.)




험프리 험프리.김현수 Delphi/C++Builder

첫번째 "델파이 마이그레이션 DAY" 회고

2019. 12. 5. 16:37

지난 화요일(12월 3일) 데브기어 라운지에서 첫번째 "델파이 마이그레이션 DAY"를 진행했습니다.


마이그레이션 데이는?

최근 윈도우 10 지원과 애플리케이션 현대화를 위한 마이그레이션 및 업그레이드를 시작하는 개발자들의 고민과 질문을 함께 고민하는 자리입니다. 해당 행사는 정기적으로 매월 첫번째 화요일에 데브기어 라운지에서 오프라인으로 진행합니다. 단순히 세미나 형식으로 정보를 받는것이 아닌 서로 자유롭게 정보와 의견을 나누는 커뮤니케이션 시간입니다.

소스코드를 직접 가져오시면 더욱 좋습니다. 전문가들의 의견을 들을 수 있습니다.


첫번째 마이그레이션 데이

첫번째 마이그레이션 데이는 5팀이 신청해 주셨고, 적은 인원으로 아주 가깝게 진행되었습니다.


각 업체에서는 당면한 기술적인 이슈에 대한 주제와 마이그레이션 시 컴포넌트 전환에 대한 주제로 자유롭게 의견을 나누었고, 저도 제 경험을 토대로 가이드를 드렸습니다.(아쉽게도 사전동의를 얻지 못해 자세한 내용 공유하지 못합니다. 다음 회고에서 남기도록 하겠습니다.)


앞으로의 계획은 

  • 마이그레이션 뿐 아니라 다양한 주제로 많은 개발자 분들과 소통하고 싶습니다.
  • 데브기어 주관이지만 개발 전문가분들의 소통의 장이 되었으면 합니다.
  • 적극적인 홍보로 많은 분들에게 알리도록 하겠습니다.

다음 마이그레이션 데이(2020년 1월 7일)에서 뵈요!!

험프리 험프리.김현수 교육, 세미나

FCM 전송 구현하기 - 앱 서버 프로토콜 사용 메시지 전송

2019. 11. 8. 15:44

FCM 메시지 수신하는 방법은 다음 링크를 참고하세요.


이 글에서는 FCM 메시지를 전송하는 방법 설명과 푸시 서버 아키텍처를 제안합니다.

  • FCM 전송하기
  • 푸시 서버 아키텍처 제안

FCM 전송하기

FCM 전송은 v1 HTTP 프로토콜(Admin SDK 이용)기존 앱 서버 프로토콜 방식을 제공합니다.


이 글에서는 기존 앱 서버 프로토콜을 이용해 FCM 전송하도록 진행합니다.


FCM 전송 API

위 링크의 문서에는 다음과 같은 요청으로 FCM을 전송합니다.


주요 항목은 다음과 같습니다.

 URI

 https://fcm.googleapis.com/fcm/send

 HTTP Method

 POST

 Header

 Authorization: key={프로젝트 서버 키}

 Content-Type=application/json

 Body 

 전송 데이터 JSON 포맷(Firebase 문서)

 {

   "to": "{디바이스 Firebase 토큰}", 

   "priority": "high",

   "notification": {

     "title": "{알림센터 제목}", 

     "body": "{알림센터 내용}"

   }, 

   "data":{

     // 사용자 정의 데이터

   }

 }


프로젝트 서버 키는 구글 Firebase 콘솔 의 프로젝트 설정 > 클라우드 메시징 화면에서 확인할 수 있습니다.



디바이스 Firebase 토큰은 안드로이드 앱에서 푸시 서비스와 연결 후 취득한 값입니다.


FCM 전송 구현

 위 내용을 구현한 화면은 다음과 같습니다.


구현된 코드는 다음과 같습니다.

procedure TForm1.Button1Click(Sender: TObject);
var
  JsonBody: TJSONObject;
begin
  RESTClient1.BaseURL := 'https://fcm.googleapis.com/fcm/send';
  RESTClient1.ContentType := 'application/json';

  RESTRequest1.Method := rmPOST;

  RESTRequest1.Params.AddItem('Authorization', 'key=' + edtServerKey.Text, pkHTTPHEADER, [poDoNotEncode]);

  RESTRequest1.ClearBody;
  JsonBody := GetFcmJsonData(edtToken.Text, edtTitle.Text, edtMessage.Text, edtData.Text);
  RESTRequest1.Body.Add(JsonBody);
  JsonBody.Free;
  RESTRequest1.Execute;

  Label1.Text := RESTResponse1.StatusCode.ToString + ' ' + RESTResponse1.StatusText;
  Memo1.Lines.Text := RESTResponse1.Content;
end;

function TForm1.GetFcmJsonData(AClientToken, ATitle, AMessage,
  AData: string): TJSONObject;
var
  Noti, Data: TJSONObject;
begin
  Result := TJsonObject.Create
    .AddPair('to', AClientToken)
    .AddPair('priority', 'high');
  Result.AddPair('notification',
    TJsonObject.Create
      .AddPair('title', ATitle)
      .AddPair('body', AMessage));
  Result.AddPair('data',
    TJSONObject.Create
      .AddPair('title', ATitle)
      .AddPair('body', AMessage)
      .AddPair('custompayload', AData));
end;


GetFcmJsonData 메소드에서 만드는 전송 데이터(JSON)의 "notification" 항목은 알림센터에 표시되는 내용입니다.

"data" 항목은 커스텀하게 구조 및 내용을 지정해 전달하면 그대로 전달됩니다. data의 title, body를 중복해 입력한 이유는 아래에서 다시 설명합니다.


위 구현은 윈도우 클라이언트로 진행했습니다. 하지만, 다른 플랫폼과 다른 프로젝트(RAD 서버, 데이터스냅 등)에서도 동일한 방식으로 구현가능합니다.


전송 결과


전송 시 위와 같이 전송 결과를 수신합니다.


현재는 전송대상을 특정 대상 하나로 지정했지만, 전송 데이터의 registration_idx  속성에 전송대상을 배열로 지정해 복수의 대상에게 전송할수도 있습니다. 자세한 전송 프로토콜은 아래 문서를 참고하시기 바랍니다.


FCM 클라이언트 수신

구현한 내용으로 FCM 전송 시 구글 FCM 서비스를 통해 기기로 메시지가 전달됩니다.

앱이 활성화 되어 있으면 OnReceiveNotification 이벤트를 통해 전달된 메시지를 수신할 수 있습니다.

앱이 활성화 되어 있지않다면, PushService.StartupNotifications 속성을 통해 메시지를 수신할 수 있습니다.


여기서 한가지 문제는 앞의 두가지 상태에서 수신된 데이터에 약간의 차이가 있다는 것입니다.
그 차이는 gcm.notification.title과 gcm.notification.body 정보의 유무입니다.

 앱이 활성화된 상태의 수신 데이터

 앱이 활성화되지 않은 상태의 수신 데이터

 


위의 수신데이터의 "title"과 "body"항목은 제가 전송시 일부로 추가한 내용입니다. 해당 항목이 없다면 비활성화 상태의 수신데이터에서는 제목과 내용은 알수 없을 것입니다.(사실 몰라도 될 수 있습니다.^^)


전송부에서 추가한 항목은 "title", "body", "custompayload"입니다. 전송 시 "data" 속성 하위에 추가한 정보들이 메시지에 함께 전달되는 것을 확인할 수 있습니다. 자식객체를 추가해 계층으로 데이터를 구성할 수도 있습니다.


이 매커니즘을 활용해 필요한 데이터 형식으로 메시지를 전송하도록 합니다.


FCM 전송 아키텍처 제안

앞에서 구현한 전송 프로그램은 디바이스 Firebase 토큰을 입력받아 진행합니다. 하지만 실제 서비스에서는 토큰을 수집하는 매커니즘이 필요합니다.


보통, 서버를 하나 두고 디바이스에서 취득한 토큰을 서버를 통해 저장 및 관리 후 저장된 토큰을 이용해 메시지를 전송해야 할 것입니다.


위 과정을 생각하다보니, 간단한 푸시 메시지 서버 제작도 가능할 것 같아 그려본 아키텍처를 소개합니다.


푸시 서버 아키텍처 구성

구성요소

  • 안드로이드 앱 : FMX 기반 앱, 푸시 서버에 따라 iOS, 윈도우 등 확장 가능
  • 관리자 앱 : 푸시 메시지를 전송하는 관리자용 앱
  • 서버 : 모노리틱 또는 마이크로 서비스 기반
    • Auth 서비스 : 인증 기능을 제공하는 서비스. 여기서는 인증 시 토큰을 받아 저장
    • Push 서비스 : 푸시 서비스 제공
  • DBMS
  • 구글 FCM 서비스


처리 흐름

  1. 앱에서 사용자 인증(또는 초기화) 시 Firebase 토큰을 전달
  2. 사용자 정보와 매핑되도록 토큰을 DB에 저장
  3. 관리자 앱에서 사용자 정보 조회
  4. 푸시 서비스에 푸시 요청. 대상으로 사용자 키값 지정
  5. 사용자 키값과 매핑되는 토큰으로 FCM 전송 요청
  6. 앱에서 푸시 수신
  7. 푸시 전송 결과 응답
  8. (옵션)전송 결과 저장


위 아키텍처는 RAD 서버를 이용해 마이크로 서비스 아키텍처로 구현하면 좋을 것 같습니다.

물론 데이터 스냅, WebBroker 등의 다른 백엔드 기술을 이용해도 구현가능합니다.


위 아키텍처는 확장가능합니다.

현재는 안드로이드 앱으로 한정지었지만, iOS 앱 또는 윈도우 앱으로 확장가능합니다.

현재는 푸시 서비스를 구글 FCM 서비스로 한정지었지만, 다른 푸시 서비스로 병렬로 운영할 수도 있습니다.

예를 들면, WNS(윈도우즈 푸시 알림 서비스)를 연동한다면 윈도우 앱을 대상으로도 푸시 메시지 전송이 가능할 것입니다.


험프리 험프리.김현수 데이터 엑세스

강동구청 특강

2019. 10. 28. 09:58


강동구는 강동구 평생 학습관에서 '코딩교육지도사' 양성과정을 진행합니다.

강동구는 손랩소프트(손랩영재교육), 데브기어, 한국생산성본부와 협약을 맺고 진행하고 있습니다.


저는 데브기어 소속으로 실무에 사용되는 기술을 교육하는 특강을 3회에 걸처 진행합니다.


1차 : RAD 스튜디오 소개 및 사용법 안내. FireDAC을 이용한 DB 연결과 데이터 컨트롤과 DB 연결
2차 : 클라우드와 OpenAPI 연동위한 REST API 이해. [실습] 카카오톡 메시지 전송 연동
3차 : 비콘을 이용한 위치기반 서비스. [실습] 위험지역 경보


1차

1차 : RAD 스튜디오 소개 및 사용법 안내. FireDAC을 이용한 DB 연결과 데이터 컨트롤과 DB 연결


1차 작성된 샘플 코드

20191028_gangdong.zip



2차

클라우드와 OpenAPI 연동위한 REST API 이해. [실습] 카카오톡 메시지 전송 연동

mysql 라이브러리 설치


2차 작성된 샘플 코드(카카오 메시지 전송 포함)

3차

비콘을 이용한 위치기반 서비스. [실습] 위험지역 경보

UUID
{E2C56DB5-DFFB-48D2-B060-D0F5A71096E0}


험프리 험프리.김현수 교육, 세미나

FMX TListView Item 높이 조절 - 이미지가 없는 경우 낮게 표시하기

2019. 10. 25. 16:24

데브기어 테크게시판의 다음 질문에 대한 답변입니다. - https://tech.devgear.co.kr/delphi_qna/456600


이 글에서는 파이어몽키 TListView 항목 추가 시 이미지를 포함하고, 이미지가 없는 경우 항목의 높이를 낮게 지정하는 방법을 설명합니다.



화면 구성


TListView의 ItemAppearance를 DynamicAppearance로 설정 후 Image와 Text를 추가했습니다.

위치 조정 후 높이는 140으로 설정했습니다.


구현

두개의 버튼을 두고 다음과 같이 구현했습니다.

procedure TForm1.Button1Click(Sender: TObject);
var
  Item: TListViewItem;
begin
  Item := ListView1.Items.Add;
  Item.Data['Text1'] := '설명';
  Item.Data['Image2'] := TBitmap.CreateFromFile('C:\Users\hjfac_000\Pictures\01.jpg');
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  Item: TListViewItem;
  Image: TListItemImage;
  Text: TListItemText;
begin
  Item := ListView1.Items.Add;
  Item.Data['Text1'] := '이미지가 없는 항목';
  Item.Height := 44;

  Text := Item.Objects.DrawableByName('Text1') as TListItemText;
  Text.PlaceOffset.Y := 12;
end;


위 화면 구성과 코드를 참고해 다양하게 항목을 구성할 수 있습니다.

험프리 험프리.김현수 파이어몽키

델파이 컴포넌트/소스 마이그레이션 자동화 도구

2019. 10. 10. 08:57

마이그레이션 자동화 도구

마이그레이션 자동화 도구는 델파이 소스파일을 분석해 컴포넌트를 변경하고, 컴포넌트를 사용한 소스코드를 일괄 변경하는 도구입니다.

이 도구를 이용해 다수의 소스파일과 다수의 컴포넌트 그리고 소스코드를 일관되게 전환할 수 있습니다.

 

마이그레이션 자동화 도구 제작 계기

이미 엠바카데로에서는 reFind라는 마이그레이션 자동화 도구를 제공합니다. reFind는 소스코드를 분석해 컴포넌트를 변경하고, 속성을 변경하고, 유즈절을 정리하는 등의 기능을 제공합니다. 하지만, reFind는 일대일로 매칭되는 컴포넌트와 속성만을 변경할 수 있습니다.

 

이 도구를 제작한 계기는 리얼그리드(TRealGrid)를 퀀텀그리드(TcxGrid)로 전환이 필요한 컨설팅 프로젝트였습니다.

퀀텀그리드는 기본적으로 3개의 컴포넌트 셋(그리드, 레벨, 뷰)으로 그리드를 구성합니다. 즉, 하나의 리얼그리드를 3개의 그리드 셋으로 변경해야 했고, 컬럼의 경우도 리얼그리드는 목록 속성으로 퀀텀그리드는 객체 목록으로 구성되어 reFind 기능으로 부족했습니다.

 

리얼그리드를 퀀텀그리드로 변환하는 작업이 작은수였다면 복잡하더라도 수작업으로 진행했을 것입니다. 하지만 해당 프로젝트에서는 천여개의 화면에 수백여개의 리얼그리드를 사용했고, 그리드를 사용하는 코드는 몇만 줄이었습니다. 이 코드를 수작업으로 진행한다면 작업기간이 오래걸릴 것은 물론, 여러명이 작업 시 서로 다른 방식으로 작업하는 등 일관되게 작업하기 어려웠습니다.

 

그래서, 기존 소스코드를 분석해 원하는 형태로 일관되게 재작성하기 위한 마이그레이션 자동화 도구를 작성했습니다.

 

비슷한 내용의 작업이 필요한 분들을 위해, 마이그레이션 자동화 도구를 오픈소스로 공개합니다.

 

마이그레이션 자동화 도구는 제공되는 소스코드를 그대로 사용할 수 없습니다. 컴포넌트와 소스코드는 사용자에 따라 매우 다양하게 사용됩니다. 마이그레이션 작업은 사용자의 패턴에 맞춰 변환하게 되고 이 도구에 구현된 코드들도 제가 진행한 프로젝트에 맞게 커스터마이징된 것입니다.(참고를 위해 그대로 등록합니다.)

 

즉, 여러분들이 위 오픈소스를 활용하려면, 마이그레이션 자동화 도구의 구조를 어느정도 이해하고 여러분들의 소스코드의 패턴과 사용방법에 맞춰 변환작업을 위한 코드를 커스터마이징해야 합니다. 다음 설명을 통해 마이그레이션 도구의 원리와 구조를 설명합니다.

  • 마이그레이션 자동화 도구의 원리
  • 마이그레이션 자동화 도구의 구조와 컨버터

 

마이그레이션 자동화 도구의 원리

마이그레이션 자동화 도구의 주요 목적은 컴포넌트 변경을 자동화하는 것입니다. 컴포넌트 변경을 자동화하기 위해서는 파일을 읽어 정보를 탐색 및 분석 후 변환할 컴포넌트 형식으로 재작성 과정이 필요합니다.

컴포넌트 정보는 폼파일(*.dfm)과 소스파일(*.pas)에 있으며 컴포넌트 변경은 다음 두가지 작업을 통해 진행해야 합니다.

폼파일 전환작업

폼파일은 dfm 확장자를 갖는 파일로 내부적으로 다음과 같은 오브젝트 텍스트 포맷의 텍스트 파일로 구성됩니다.

오브젝트 텍스트 포맷의 특징은 다음과 같습니다.

  • 컴포넌트 정보는 object로 시작합니다.
  • 들여쓰기를 통해 시작과 끝(end)을 구분합니다.
  • 속성은 "이름 = 값" 형식으로 구성됩니다.
  • 컴포넌트의 목록 속성은 목록이름 은 "이름 = <>" 형식을 갖습니다.

마이그레이션 자동화 도구는 위의 오브젝트 텍스트를 분석하는 파서를 포함하고 있습니다.

파서를 통해 컴포넌트 내용을 분석 후 새로운 컴포넌트 정보로 재작성해 파일에 덮어쓰게 됩니다.

소스파일

소스파일에서 컴포넌트를 변경하려면 다음 작업을 진행해야 합니다.

  • 선언부의 컴포넌트 정보(클래스명)를 변경(또는 추가)해야 합니다.
  • 유즈절의 관련 유닛을 변경해야 합니다.
  • 컴포넌트 이벤트를 변경된 컴포넌트 이벤트와 재 매핑해야 합니다.
    • 이벤트 메소드의 파라메터 변경에 대응해야 합니다.
  • 컴포넌트를 사용한 소스코드를 찾아 변경해야 합니다.
    • 컴포넌트 속성 및 메소드를 사용하는 코드를 변경해야 합니다.
    • 컴포넌트를 사용한 변수와 파라메터 변수의 타입을 변경해야 합니다.

리얼그리드를 퀀텀그리드로 변경한 예시를 통해 자세히 살펴봅니다.

컴포넌트 정보 변경

 리얼그리드  퀀텀그리드
RealGrid1: TRealGrid; RealGrid1: TcxGrid;
RealGrid1Level1: TcxGridLevel;
RealGrid1BandedTableView1: TcxGridBandedTableView;
RealGrid1BandedTableView1BandedColumn1: TcxGridBandedColumn;
RealGrid1BandedTableView1BandedColumn2: TcxGridBandedColumn;
RealGrid1BandedTableView1BandedColumn3: TcxGridBandedColumn;
RealGrid1BandedTableView1BandedColumn4: TcxGridBandedColumn;
RealGrid1BandedTableView1BandedColumn5: TcxGridBandedColumn;

리얼그리드 컴포넌트를 퀀텀그리드로 변경하려면 여러개의 컴포넌트로 분리해야 합니다. 퀀텀그리드 특성상 그리드, 레벨, 뷰로 나뉘고 컬럼들도 개별 객체로 구성하도록 변경해야 합니다.

 

이벤트 재 매핑

리얼그리드의 이벤트와 퀀텀그리드의 이벤트는 일대일 매칭이 되지 않습니다. 최대한 비슷한 이벤트로 매핑 후 코드를 추가해 비슷한 기능을 구현해야 합니다.

 

 리얼그리드 procedure RealGrid1ValueChanged(AColumn: TwMemColumn);
procedure FormShow(Sender: TObject);
procedure RealGrid1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
 퀀텀그리드 procedure cxGrid1BandedTableView1EditValueChanged(
  Sender: TcxCustomGridTableView; AItem: TcxCustomGridTableItem);
procedure cxGrid1BandedTableView1EditKeyDown(
  Sender: TcxCustomGridTableView; AItem: TcxCustomGridTableItem;
  AEdit: TcxCustomEdit; var Key: Word; Shift: TShiftState);

위 코드 예시를 보면 ValueChanged 이벤트의 경우 리얼그리드는 그리드 자체에 쿼텀그리드는 View의 EditValueChanged 이벤트와 매핑해야 합니다.

또한 이벤트 메소드의 파라메터가 다른 경우가 많습니다.

이벤트 변경을 위해서는 소스파일에서 이벤트 메소드 선언 및 구현 후, 폼파일에서 컴포넌트 이벤트와 메소드를 연결해야 합니다.

 

컴포넌트 사용한 소스 변경

컴포넌트를 사용한 소스코드를 찾아 변경할 컴포넌트에 맞도록 코드를 재작성하는 작업을 해야 합니다.

이 과정은 가장 양이 많고, 복잡하며, 코딩 스타일에 따라 경우의 수가 매우 다양할 수 있습니다.

 

자동화하기 위해서는 다음 순서로 작업해야 합니다.

  1. 변경해야할 코드를 찾고, 어떻게 변경할지 패턴을 찾습니다.
  2. 정규표현식을 이용 변경 대상 코드를 탐색합니다.
  3. 찾은 코드의 정보를 이용해 변경할 코드를 재작성합니다.

 

다음 코드는 그리드의 특정 컬럼을 읽기전용 설정하는 패턴입니다.

 리얼그리드 RealGrid1.Columns[02].ReadOnly:=True;
 퀀텀그리드 RealGrid1BandedTableView1.Columns[02].Properties.ReadOnly := True;

 

위의 리얼그리드 소스를 퀀텀그리드 소스로 변경하려면 2단계로 진행해야합니다.

1) RealGrid1.Columns[을 RealGrid1BandedTableView1.Columns[으로 변경해야 합니다.

2) ].ReadOnly를 찾아 ].Properties.ReadOnly로 변경해야 합니다.

(주의할 점은 대소문자를 구분하지 않고 이 작업을 진행해야 합니다.)

 

위 과정을 정규표현식으로 처리한 코드는 다음과 같습니다.(자세한 내용은 뒤에서 다시 설명합니다.)

function TColumnsConverter.ConvertColumnsItems(ASrc: string;
  var ADest: string): Boolean;
const
  SEARCH_PATTERN  = '(' + GRIDNAME_REGEX + '\.[Cc]olumns\[|' + GRIDNAME_REGEX + '\.[Cc]olumns.Items\[)';
  REPLACE_FORMAT  = '[[COMP_NAME]]BandedTableView1.Columns[';
var
  I: Integer;
  Matchs: TMatchCollection;
  Match: TMatch;
  Comp, Src, Dest: string;
  TagStartIdx, TagEndIdx: Integer;
begin
  Result := False;

  if not (ASrc.Contains('.Columns[') or ASrc.Contains('.columns[')
      or ASrc.Contains('.Columns.Items[') or ASrc.Contains('.Columns.items[')
      or ASrc.Contains('.columns.Items[') or ASrc.Contains('.columns.items[')
    ) then
    Exit;

  Matchs := TRegEx.Matches(ASrc, SEARCH_PATTERN, [roIgnoreCase]);
  if Matchs.Count = 0 then
    Exit;
  ADest := ASrc;
  for I := 0 to Matchs.Count - 1 do
  begin
    Match := Matchs[I];
    Src := Match.Value;

    Comp  := TRegEx.Match(Src, '[a-zA-Z\d\_]+\.').Value.Replace('.', '').Trim;

    Dest := REPLACE_FORMAT;
    Dest := Dest.Replace('[[COMP_NAME]]', Comp);

    ADest := ADest.Replace(Src, Dest);
    Result := True;
  end;
end;

function TColumnsConverter.ConvertColumnsReadOnly(ASrc: string;
  var ADest: string): Boolean;
const
  SEARCH_PATTERN  = '\]\.[Rr]ead[Oo]nly';
  REPLACE_FORMAT  = '].Properties.ReadOnly';
var
  I: Integer;
  Matchs: TMatchCollection;
  Match: TMatch;
  Src, Dest: string;
  TagStartIdx, TagEndIdx: Integer;
begin
  Result := False;

  if (not ADest.Contains('Columns[')) and (not ADest.Contains('columns[')) then
    Exit;

  Matchs := TRegEx.Matches(ASrc, SEARCH_PATTERN, [roIgnoreCase]);
  if Matchs.Count = 0 then
    Exit;
  ADest := ASrc;
  for I := 0 to Matchs.Count - 1 do
  begin
    Match := Matchs[I];
    Src := Match.Value;

    Dest := REPLACE_FORMAT;

    ADest := ADest.Replace(Src, Dest);
    Result := True;
  end;
end;

 

위와 같이 간단한 1줄을 변경하기 위해 꽤 긴줄의 코드를 작성해야 합니다.

하지만, 한번 작성해 놓으면 해당 패턴이 1번이건 100번이건 또는 다시 작업하더라도 부담이 없어집니다.

 

이와 같이 소스코드를 분석해 패턴을 찾고, 그 패턴을 처리하는 코드를 추가하는 작업을 지속적으로 반복해야 합니다.

마이그레이션 자동화 도구 구조와 컨버터

마이그레이션 자동화 도구는 컴포넌트를 변환하는 CompMigrationTool과 소스파일을 변환하는 SrcMigrationTool 두가지가 제공됩니다.

 

두가지 도구의 공통점은 비슷한 인터페이스(UI)와 컨버터를 이용한다는 점입니다.

 

인터페이스

두 도구 모두, 지정된 경로에서 파일을 불러오면 좌측 목록에 표시됩니다. 우측 목록에는 컨버터 목록이 표시됩니다. 

 

컨버터

컨버터란 실질적인 변환작업을 하는 기능으로, 다양한 마이그레이션 코드를 기능별로 분리하고, 구현을 분리하는 역할을 합니다.

 

마이그레이션 도구를 이용한다면, 기존 컨버터에 기능을 추가하거나, 컨버터 클래스를 상속받아 새로운 컨버터를 개발해 추가할 수 있습니다.

 

두가지 도구 모두 TConverterManager와 TConverter 클래스 제공합니다. TConverter 클래스는 상속받아 변환작업을 구현하는 베이스 클래스 역할을 합니다. TConverterManager에 컨버터를 등록하면 우측 목록에 컨버터가 추가되어 선택해 해당 작업을 수행할 수 있습니다.

 

컴포넌트 변환 도구(CompMigrationTool)

컴포넌트 변환 도구의 목적은 폼파일과 소스파일의 컴포넌트 정보를 찾아 변경하는 것입니다.

 

주요 작업 순서는 다음과 같습니다.

  1. 폼파일에서 컴포넌트 문자열을 찾아 추출합니다.
  2. 컴포넌트 문자열을 분석해 속성, 이벤트 등의 정보를 추출합니다.
  3. 추출한 컴포넌트 정보로 새로운 컴포넌트 정보를 작성합니다.
  4. 기존 컴포넌트 문자열에 새로운 컴포넌트 정보를 덮어씁니다.

 

가장 핵심이 되는 작업은 2번 단계의 컴포넌트 문자열을 분석하는 파서새로운 컴포넌트 정보를 작성하는 치환 작업입니다.

 

컴포넌트 문자열을 분석하는 파서

TObjectTextParser 클래스를 통해 문자열 분석 작업을 합니다. 이 클래스는 델파이에 내장된 System.Classes.ObjectTextToBinary 함수를 참고해 작성되었습니다.

 

다음과 같이 문자열 분석(Parse) 후 속성정보(Properties.Values[])를 읽어 오도록 구현되었습니다.

 

리얼그리드를 분석하는 작업은 컬럼, 밴드 등의 복잡한 구조를 분석하기 위해 TObjectTextParser를 상속해 TRealGridParser 클래스를 작성했습니다.

 

새로운 컴포넌트 정보를 작성하는 치환 작업

파서를 통해 분석한 정보로 새로운 컴포넌트 정보를 작성하는 작업은 미리 컴포넌트 문자열 템플릿을 만들고 필요한 값을 치환하도록 진행했습니다.

 

다음과 같이 퀀텀그리드(TcxGrid) 컴포넌트 문자열 템플릿 상수를 정의 했습니다.

 

다음(RealGridToCXGridConverter.GetConvertedCompText)과 같이 파서로 분석한 정보로 치환합니다.

컨버터를 통핸 작업 분리

컴포넌트 정보를 분석하고, 재작성하는 코드는 컴포넌트마다 다르게 작성해야 합니다.

 

컨버터 클래스(TConverter)를 상속받아 컴포넌트 변환 작업 별 컨버터를 재작성 합니다.

컨버터 클래스의 protected의 virtual 메소드를 전환 대상에 맞게 필요한 메소드를 재 구현해야합니다.

상속받아 재구현 후 파일의 끝 initialization에 TConverterManager에 등록하는 코드를 추가하면, 메인화면의 컨버터 영역에 해당 컨버터가 표시되어 사용할 수 있습니다.

소스파일 변환 도구(SrcMigrationTool)

소스파일 변환 도구의 목적은 소스파일을 읽어 변환할 대상인지 판단 후 변환하는 것입니다.

 

주요 작업순서는 다음과 같습니다.

  1. 소스파일을 로딩 후 컴포넌트를 사용하는 소스파일인지 판단합니다.
  2. 구현부(implementation) 부터 소스코드 라인별로 변환처리를 반복합니다.
  3. 정규표현식으로 해당 소스코드 라인이 변환 대상인지 판단합니다.
  4. 정규표현식으로 찾은 정보(컴포넌트 이름 등)를 이용 변환할 문자열을 작성합니다.
  5. 정규표현식으로 찾은 문자열 부분을 변환한 문자열로 치환합니다.
  6. 변환된 소스코드 라인을 기존 라인에 덮어씁니다.

가장 핵심이 되는 기능은 정규표현식으로 변환대상 판단 후 변환한 문자열로 치환하는 작업입니다

 

 

정규표현식

소스파일 변환 도구는 정규표현식을 이용해 변환 대상을 판단합니다. 다양한 방식으로 작성된 소스코드는 단순한 패턴으로 검색 및 치환 시 의도치 않은 내용이 변경될 수 있습니다. 정규표현식을 이용 정교한 검색이 필요합니다.

 

다음은 리얼그리드의 "GridName.Cells[].As***" 속성을 사용하는 구문 예시(패턴)입니다.

RealGrid1.Cells[00,RealGrid1.Row].AsString := FieldByName('품번'      ).AsString;
Cells[숫자, 변수]
RealGrid3.Cells[28,Row].AsFloat := FieldByName('부가세단가').AsFloat;
좌항 := 우항
RealGrid2.Cells[45,Row].AsFloat  := FieldByName('부가세단가').AsFloat*RealGrid1.Cells[21,Row].AsFloat;
우항, 함수내
SQLText2 := StringSQLReplace('S',SQLText2,'품번',   RealGrid1.Cells[62,Row].AsString);
조건식 내
if (RealGrid1.RowCount > 0) and (SW_Func_CharDelete(RealGrid1.Cells[02, RealGrid1.RowCount-1].AsString,'-') = '') then Begin
함수 내, 복수
SQLText := StringSQLReplace('S',SQLText,'반출판매구분'    ,Copy(RealGrid1.Cells[24,i].AsString,1,Pos(':',RealGrid1.Cells[24,i].AsString)-1));
Cells[속성, 속성]
(RealGrid1.Cells[AColumn.Index, RealGrid1.Row].Value <> Null) then
with 문 사용
Cells[3,J].AsFloat := Default_DBBandedTableView.DataController.Values[I,3] - Cells[1,J].AsFloat;

 

위 예시를 분석하면 다음 특징을 찾을 수 있습니다.

  • 그리드 이름이 다를 수 있습니다.
  • 한 문장에 구문이 여러번 포함될 수 있습니다.
  • 구문은 좌항에 위치할 수도 우항에 위치할 수도 있습니다.
  • with문으로 그리드 이름이 생략될 수도 있습니다.

 

위와 같은 다양한 조건에서 원하는 패턴의 문자열을 정규표현식으로 찾아야 합니다.

다음은 위 구문에서 "GridName.Cells[].As***"을 찾는 정규표현식입니다.(정규표현식에 대한 설명은 생략합니다.)

([a-zA-Z]+\d+\.)?[Cc]ells\[[a-zA-Z0-9\.\s\-\+\_]+\,[a-zA-Z0-9\.\s\-\+\_]+\]\.[a-zA-Z]+

 

정규표현식을 만들기 위해서는 소스코드에서 다양한 형식의 문장을 수집후 정규표현식 테스트 사이트 등에서 정규표현식 작성 및 검증합니다.

 

변환대상 판단 및 변환한 문자열로 치환

정규표현식으로 변환대상을 판단했다면, 대상문자열 정보를 이용해 변환할 문자열을 만들어 치환합니다.

주의할 사항은 한문장에 여러개의 변환대상이 존재할 수 있습니다.

 

다음은 리얼그리드의 Columns 속성을 퀀텀그리드 뷰의 Columns로 변환하는 코드입니다.

uses System.RegularExpressions;

function TColumnsConverter.ConvertColumnsItems(ASrc: string;
  var ADest: string): Boolean;
const
  SEARCH_PATTERN  = '(' + GRIDNAME_REGEX + '\.[Cc]olumns\[|' + GRIDNAME_REGEX + '\.[Cc]olumns.Items\[)';
  REPLACE_FORMAT  = '[[COMP_NAME]]BandedTableView1.Columns[';
var
  I: Integer;
  Matchs: TMatchCollection;
  Match: TMatch;
  Comp, Src, Dest: string;
  TagStartIdx, TagEndIdx: Integer;
begin
  Result := False;

  Matchs := TRegEx.Matches(ASrc, SEARCH_PATTERN, [roIgnoreCase]);
  if Matchs.Count = 0 then
    Exit;
  ADest := ASrc;
  for I := 0 to Matchs.Count - 1 do
  begin
    Match := Matchs[I];
    Src := Match.Value;

    Comp  := TRegEx.Match(Src, '[a-zA-Z\d\_]+\.').Value.Replace('.', '').Trim;

    Dest := REPLACE_FORMAT;
    Dest := Dest.Replace('[[COMP_NAME]]', Comp);

    ADest := ADest.Replace(Src, Dest);
    Result := True;
  end;
end;

델파이에서 정규표현식을 사용은 TRegEx(uses System.RegularExpressions)를 이용합니다.

TRegEx.Matches 함수를 호출하면 지정된 패턴과 일치하는 목록(Collection)을 받을 수 있습니다. 일치하는 내용은 Matchs[I].Value로 읽어 올 수 있습니다.

 

일치하는 내용 중 컴포넌트 이름 등을 추출 후 미리정의된 템플릿 문장(REPLACE_FORMAT)을 이용 치환해 변환할 문자열을 만듭니다.

이후 찾은 문자열을 변환한 문자열로 치환합니다.

 

원리와 구조를 설명하려다보니 글이 길어지고 어려워졌습니다. 그래도 내용이 잘 전달되었으면 합니다.

이 글과 도구에 대한 궁금한 사항은 댓글로 질문 부탁드립니다.

 

그리고, 제가 소속된 데브기어에서는 마이그레이션 컨설팅과 워크샵 과정을 운영하고 있습니다.

마이그레이션 컨설팅은 고객사에 방문해 전문 컨설턴트가 주도하며 마이그레이션을 진행합니다.

마이그레이션 워크샵은 5일간 데브기어 교육장에서 여러분들의 소스코드를 가져와 전문가의 도움을 받으며 시작할 수 있습니다.

그외 다양한 마이그레이션 자료도 참고자료 링크를 통해 확인하시기 바랍니다.

 

참고자료

험프리 험프리.김현수 Delphi/C++Builder 델파이, 마이그레이션, 업그레이드

엔터프라이즈 커넥터로 VCL 애플리케이션에서 '구글 시트' 데이터 조회 및 편집하기

2019. 10. 2. 17:12

엔터프라이즈 커넥터(Enterprise Connectors)는 약 130여종의 기업용 데이터에 표준 SQL을 통해 접근 및 연동할 수 있는 솔루션 입니다. 

 

이 글에서는 구글 드라이브의 스프래드 시트인 구글 시트(Google Sheets)와 양방향 엑세스하는 기능을 직접 구현해보겠습니다. 

 

구글 시트는? 

  • 구글 드라이브에서 제공하는 웹 기반 스프래드 시트 프로그램입니다. 
  • 엑셀과 같은 스프래드 시트를 온라인에서 사용할 수 있습니다. 
  • 문서를 그룹 또는 사용자와 공유하고, 권한 설정 가능합니다. 

이 글에서는 다음 내용을 설명합니다.

  • 엔터프라이즈 커넥터 설치
  • 구글 API 콘솔 설정
  • 엔터프라이즈 커넥터 문서 확인
  • 구글 시트 연동
    • TFDConnection으로 구글 시트 연결
    • TFDQuery로 정보 조회, 입력, 수정, 삭제
    • TFDStoredProc으로 새로운 시트 추가(추가 제공) 기능 사용
  • 배포

엔터프라이즈 커넥터 설치

다음 링크를 참고해 엔터프라이즈 커넥터 중 "Google Sheets"를 설치합니다.

 

구글 API 콘솔 설정

구글 드라이브의 구글 시트에 연동을 위해서는 구글 API 콘솔에서 앱 및 인증정보를 등록과 API 활성화해야 합니다.

 

구글 API 콘솔 접속 및 프로젝트 선택

다음 링크를 통해 구글 API 콘솔 화면에 접속합니다.(구글 계정이 필요합니다.)

프로젝트를 선택하거나 새 프로젝트를 생성합니다.

사용자 인증 정보 만들기

OAuth 클라이언트 ID 생성 후 클라이언트 ID와 클라이언트 시크릿을 생성합니다.

이 두개의 값은 향후 구글 시트와 연결 시 사용합니다.

 

API 및 서비스 > 사용자 인증 정보 화면으로 이동합니다.

 

사용자 인증 정보 만들기 > OAuth 클라이언트 ID 항목을 선택합니다.

애플리케이션 유형 > 기타 선택 후 구분가능한 이름을  입력 후 [생성] 버튼을 클릭합니다.

OAuth 2.0 클라이언트 ID 생성 후 클라이언트 ID와 클라이언트 보안 비밀 항목을 확인합니다.

(이 2개 값은 연동 시 활용됩니다.)

 

구글 드라이브 & 구글 시트 API 활성화

구글 드라이브와 구글 시트를 사용하도록 API 활성화를 진행합니다.

 

API 및 서비스 > 라이브러리 메뉴 선택 후 검색란에 검색어 "Sheets" 를 입력하고 "Google Sheets API" 항목을 선택합니다.

[사용 설정] 버튼을 눌러 사용 설정합니다.

"Google Drive API"도 위와 동일한 방법으로 활성화 진행합니다.

 

엔터프라이즈 커넥터 문서

구글 시트와 같은 서비스 연동은 일반적으로 서비스에서 제공하는 API를 이용해 연동합니다.

하지만, FireDAC 엔터프라이즈 커넥터는 기존 DBMS 연동과 비슷하게 FireDAC 컴포넌트의 쿼리와 스토어드 프로시저 컴포넌트를 이용해 연동하는 방식을 제공합니다.

 

표준 SQL 문을 통해 원하는 데이터에 접근하고, 스토어드 프로시저로 원하는 기능을 호출합니다.

서비스 별 테이블과 필드에 대한 정보 그리고 기능(스토어드 프로시저)은 엔터프라이즈 커넥터 문서를 참고해야 합니다.

주로 살펴볼 내용은 다음과 같습니다.

 

SQL Compliance

엔터프라이즈 커넥터는 표준 SQL 문법을 통해 서비스의 데이터에 접근합니다.

 

SELECT 문법의 경우 SELECT FROM WHERE 절 뿐아니라, JOIN, UNION, GROUP BY, ORDER BY 등의 문법을 사용할 수 있습니다.

INSERT, UPDATE, DELETE 등의 문장을 통해 데이터 즉, 새로운 행을 추가, 수정, 삭제 할 수 있습니다.

다음과 같은 예제들을 참고할 수 있습니다.

Stored Procedures

새로운 시트 추가(CreateSpreadsheet), 시트 복사(CopySheet)와 같은 추가 기능은 Stored Procedure를 통해 제공합니다.

구글 시트 연동

연동은 VCL Form Application 프로젝트로 진행합니다.

구글 시트 연결 설정

폼에 TFDConnection 컴포넌트를 추가하고, Driver ID를 "CDataGoogleSheets"로 선택 후 연결 속성을 설정합니다.

  • InitiateOAuth : GETANDREFRESH(연결 토큰이 존재하지 않으면 브라우저를 통해 사용자에게 토큰을 얻음)
  • OAuthClientId : 구글 API 콘솔의 OAuth 2.0 클라이언트 ID의 클라이언트 ID 입력
  • OAuthClientSecret : 구글 API 콘솔의 OAuth 2.0 클라이언트 ID의 클라이언트 보안 비밀 입력
  • OAuthSettingsLocation : 인증결과 정보를 기록하는 파일 경로

구글은 OAuth 2.0을 이용 사용자 인증 및 서비스 접근 권한을 확인합니다.

OAuth 2.0 연동 과정은 엔터프라이즈 커넥터 내부적으로 진행합니다. 연결을 활성화하면 자체적으로 웹브라우저를 실행 후 인증 및 권한 확인 절차가 수행됩니다.

 

이때, 인증은 사용자의 계정으로 진행되며 이후 진행되는 구글 시트 등은 인증한 사용자의 문서와 연동됩니다.

즉, 개발 시 연동하는 문서는 개발자의 구글 시트 문서이며, 실제 사용 시에는 사용자의 문서와 연동 될것입니다.

 

FireDAC Connection Editor에서 [Test] 버튼을 누르거나, TFDConnection의 Connected 속성을 True로 설정해 연결합니다.

 

(옵션) 등록 후 구글측의 확인 절차가 필요합니다. 그 전까지 다음 화면과 같이 표시됩니다. 확인 전까지 "고급 > 앱으로 이동"을 선택 해 진행합니다.

구글 드라이브와 구글 시트 권한 요청 화면, 선택사항 확인 화면에서 권한을 허용합니다.

권한을 허용하면 성공 화면이 표시됩니다.

위 과정은 개발 시 구글 시트에 연결할때도 표시되지만, 최종 사용자가 구글 시트와 연결 시에도 동일한 절차로 진행됩니다.

정보 조회/입력/수정/삭제

다음과 같이 폼을 만들었습니다.(버튼 아래의 상자는 TListBox 입니다.)

테이블 목록과 필드 목록 조회

구글 시트에는 여러개의 문서를 문서별 여러개의 시트를 제공합니다. 엔터프라이즈 커넥터에서 테이블은 각 문서의 시트를 대상으로 합니다.

다음 코드를 통해 테이블(문서의 시트) 목록을 가져옵니다. 지정된 테이블의 필드 목록도 가져옵니다.

procedure TForm1.Button2Click(Sender: TObject);
begin
  FDConnection1.GetTableNames('CDATA', '', '', ListBox1.Items);
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  if ListBox1.ItemIndex > -1 then
    FDConnection1.GetFieldNames('CDATA', '', ListBox1.Items[ListBox1.ItemIndex], '', ListBox2.Items);
end;

결과는 다음과 같습니다.

참고> 위 테이블 리스트 등을 가져오는 작업은 문서와 문서의 시트가 많다면 시간이 오래걸릴 수 있습니다.

위 기능은 테이블 목록을 가져와 표시하는 기능으로 실제 구현 시에는 테이블(문서의 시트)를 직접 확인해 SQL 문을 만드는 등으로 활용하시기 바랍니다.

 

SQL 열기 및 수행

테이블과 필드 정보를 확인할 수 있습니다.

 

시트의 정보를 TFDQuery 컴포넌트를 이용 표준 SQL 문장을 이용해 조회 및 편집할 수 있습니다.

다음은 SELECT 문과, INSERT 문을 수행한 결과입니다.

 

새로운 시트 추가

새로운 시트 추가등의 추가 기능은 TFDStoredProc 컴포넌트를 이용할 수 있습니다.

다음은 서비스에 정의된 기능 목록입니다.

새로운 시트 추가는 CreateSpreadseet 스토어드 프로시저를 이용합니다. 코드는 다음과 같습니다.

procedure TForm1.Button4Click(Sender: TObject);
var
  Title: string;
begin
  Title := InputBox('Create spread sheet', 'Spread sheet name', '');
  if Title <> '' then
  begin
    FDStoredProc1.ParamByName('Title').Value := Title;
    FDStoredProc1.ParamByName('Description').Value := 'Created with FDEC';
    FDStoredProc1.ExecProc;
  end;
end;

 

배포

개발이 완료되었다면, 라이선스를 포함해 배포 해야합니다.

 

라이선스 등록 없이 배포 시 다음 오류를 발생합니다.

라이선스 배포는 연결 속성의 RTK 항목을 통해 적용할 수 있습니다.

RTK 항목에 입력할 값은 설치 디렉토리에 포함된 deployment_licensing.txt 파일을 참고합니다.

(정식사용자의 경우 설치 시 해당 라이선스 파일이 생성됩니다.)

파일을 열면 설명과 함께 RTK 값이 포함되어 있습니다. 파일의 내용을 복사 해 연결 속성의 RTK 값 설정 후 다시 빌드해 배포할 수 있습니다.

 

기타

오류 대응

Use Spreadsheet/Folder connection property to avoid 429 and list subset of sheets.

테스트 중 위와 같은 오류가 발생해 원인 파악한 내용을 공유합니다.

문서가 매우 많은 환경에서 목록을 가져오거나 데이터 조회 시 발생했습니다.

429 응답코드는 너무 많은 요청 시 응답합니다. 추측하면 문서 목록등 조회 시 각 문서와 시트 별로 요청을 하는 것으로 예상되며 그 요청 횟수가 지정된 요청횟수 한도를 넘어 오류를 발생한 것으로 보입니다.

 

구글 API 콘솔에 다음과 같은 항목이 있습니다.(구글 API 콘솔 > API 및 서비스 > OAuth 동의 화면)

해결 방안은 위 한도 늘리기 버튼을 통해 한도를 늘리도록 설정합니다.

 

실제 서비스 전에는 다양한 테스트를 진행하며, 한도 등의 서비스 설정을 꼼꼼히 해야 할 것 같습니다.

 

험프리 험프리.김현수 Delphi/C++Builder

[RAD서버] JSON 처리 단순화 컴포넌트 활용

2019. 9. 19. 09:14

RAD 서버는 REST 기반 멀티티어 아키텍처를 구현할 수 있는 통합 미들웨어 서버입니다.

RAD 서버를 이용해 REST 기반 서비스 애플리케이션을 신속하게 구축하고 배포할 수 있습니다. - 자세히보기

 

RAD 서버는 REST 엔드포인트 제공, 통합 미들웨어, 응용 프로그램 서비스 등 다양한 기능을 제공하지만, 핵심 기능은 다양한 데이터를 JSON 포맷으로 입출력 처리하는 REST 엔드포인트 제공 기능입니다.

이 글에서는 RDBMS 데이터를 JSON 포맷으로 손쉽게 제공하는 TEMSDataSetResource 컴포넌트와 실무 활용방안을 설명합니다.

 

이 글에서 다음 내용을 설명합니다.

1) 데이터셋 JSON 처리 자동화하기 - TEMSDataSetResource 컴포넌트 소개

2) 자동화에 비지니스 로직 추가하기 - 엔드포인트 메소드 이용

3) 목록과 상세를 다른 항목으로 제공하도록 처리하는 방법

 

REST 아키텍처가 생소하거나, RAD 서버가 익숙하지 않다면, 하단 추가정보의 REST API 소개 및 구현방안을 먼저 살펴보길 권장합니다.

 

데이터셋 JSON 처리 자동화하기

TEMSDataSetResource 컴포넌트(이하 데이터셋 리소스)는 데이터셋(DB 테이블)에 대한 JSON 처리 작업을 자동화 하는 컴포넌트입니다.(RAD 스튜디오 10.3 버전부터 지원)

TEMSDataSetResource 컴포넌트의 주요 특징은 다음과 같습니다.

  • 데이터셋 JSON 처리 자동화
  • 허용 액션(AllowAction: List, Get, Post, Put, Delete) 지정
  • 목록 요청 시 페이징 처리(PageParamName, PageSize) 및 정렬 지정(SoringParamPrefix)
  • 제공할 항목 지정(ValueFields)

먼저, 데이터셋 JSON 처리 자동화 기능은 지정된 데이터셋(TFDQuery 등)의 데이터를 JSON으로 제공(List, Get)하고, 요청한 JSON 데이터를 데이터셋에 적용(Post, Put, Delete)하는 작업을 컴포넌트 단에서 처리합니다.

 

기존에는 엔드포인트 메소드를 직접 구현 해 위 작업을 진행했습니다.

 

기존의 엔드포인트 메소드 구현 방식과 비교해봅니다.

 

기존 엔드포인트 메소드 방식은 다음과 같이 구현합니다.

Get 엔드포인트 메소드를 비교하면, Get 엔드포인트 메소드를 추가하고, 데이터베이스의 내용을 조회 후 JSON 포맷의 데이터를 직접 만들어 반환합니다.

type
  [ResourceName('books')]
  TBookResource1 = class(TDataModule)
    FDConnection1: TFDConnection;
    qryBook: TFDQuery;
  published
    procedure Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
  end;

procedure TBookResource1.Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  writer: TJsonTextWriter;
begin
  writer := AResponse.Body.JSONWriter;
  
  Writer.WriteStartObject;
  Writer.WritePropertyName('books'); 

  Writer.WriteStartObject;
  Writer.WritePropertyName('total');
  Writer.WriteValue(qryBook.RecordCount);

  Writer.WritePropertyName('book');
  Writer.WriteStartArray;
  
  qryBook.Open;
  while not qryBook.Eof do
  begin
    Writer.WriteStartObject;
    Writer.WritePropertyName('BOOK_SEQ');
    Writer.WriteValue(qryBook.FieldByName('BOOK_SEQ').AsInteger);

    // JSON 필드 설정 생략

    Writer.WriteEndObject;
    qryBook.Next;
  end;
  
  Writer.WriteEndArray;

  Writer.WriteEndObject;
  Writer.WriteEndObject;
    
  AResponse.Body.SetValue(Writer.JSON as TJSONValue, True);
end;

 

데이터셋 리소스를 이용하는 방식은 다음과 같습니다.

TEMDDataSetResource 컴포넌트 추가 후, DataSet을 연결 및 제공할 행위인 AllowAction을 지정, 선언부에 ResourceSuffix 특성(Attribute) 추가. 끝~

type
  [ResourceName('books')]
  TBookRentalResource1 = class(TDataModule)
    FDConnection1: TFDConnection;
    qryBook: TFDQuery;
    [ResourceSuffix('list', '/')]
    EMSDataSetResource1: TEMSDataSetResource;
  end;

위 두가지 방식의 결과가 동일하지는 않지만 목적은 동일합니다. 데이터베이스의 내용을 질의해 그 결과를 JSON으로 제공합니다.

위 예제는 목록 데이터에 대한 것만 구현했습니다. 입력(Post), 추가(Put), 삭제(Delete) 등의 기능을 구현하면 구현해야할 코드의 차이는 어마어마 합니다.

 

즉, 데이터셋 리소스를 사용하면 테이블의 CRUD 작업을 매우 간단하고 쉽게 구현할 수 있습니다.

또한, 페이징과 정렬 등의 부가 기능도 속성 설정만으로 사용 가능합니다.

페이징

PageParamName과 PageSize 속성을 지정하면, 다음과 같은 요청으로 페이징 처리 가능합니다.

  • http://localhost:8080/books?page={페이지 번호}

정렬

SortingParamPrefix 지정 후 다음과 같이 요청 시 정렬된 데이터를 제공합니다.

  • http://localhost:8080/books?sf{정렬할 필드명}={A 또는 D}
    • sf : SortingParamPrefix에 설정된 값
    • {정렬할 필드명} : 정렬할 대상 테이블의 필드이름
    • {A 또는 D} : 정렬방식, A(Ascending: 오름차순), D(Descending: 내림차순)
  • 예> http://localhost:8080/books?sfBook_Title=A

 

결론, 데이터셋 리소스를 사용하면 기존 엔드포인트 메소드 사용시보다 코드의 양을 대폭 줄일 수 있습니다.

 

다음으로는 제가 직접 사용하며 필요한 기능들을 추가 구현한 내용을 보강합니다.

 

자동화 비지니스 로직 추가하기

데이터셋 리소스는 엔드포인트 요청 시 데이터 처리 작업을 자동으로 진행해 편리합니다.

하지만 일부 데이터의 경우 데이터를 변환하거나, 유효성 검증 등의 비지니스 로직을 데이터 처리 이전에 구현할 필요가 있습니다.

 

처리 작업 전 비지니스 로직을 추가하기 위해서는, 기존의 엔드포인트 메소드와 데이터셋 리소스를 함께 사용해야 합니다.

 

기본 원리는 엔드포인트 메소드에서 비지니스 로직 처리 후 데이터셋 리소스의 엔드포인트 메소드를 호출하는 방식입니다.

 

다음 예제는 수정 요청 시 유효성 체크 후 데이터 처리하는 코드입니다.

엔드포인트 메소드(Put=PutItem=수정)를 통해 비지니스 로직을 구현합니다.

데이터셋 리소스의 엔드포인트 메소드를 호출 합니다. 매개변수를 그대로 전달합니다.

procedure TTestResource1.PutItem(const AContext: TEndpointContext;
  const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  Json: TJSONValue;
  Title: string;
begin
  JSON := ARequest.Body.GetValue;
  Title :=JSON.GetValue<string>('BOOK_TITLE');

  if Title = '' then
    AResponse.RaiseBadRequest;

  EMSDataSetResource1.Put(AContext, ARequest, AResponse);
end;

 

 

데이터셋 리소스의 엔드포인트 메소드는 다음과 같습니다.

  • List - 목록 요청
  • Get - 상세 요청
  • Post - 신규 생성
  • Put - 항목 수정
  • Delete - 항목 삭제

목록과 상세를 다른 항목으로 제공하기

데이터 요청은 목록을 요청하거나 상세를 요청할 수 있습니다. 도서정보를 예로 들면 목록은 "/books" 상세는 "/books/3" 등으로 요청합니다.

 

응답은 일반적으로 JSON 포맷으로 제공하며, 이때 목록과 상세의 항목을 동일하게 제공할 수도, 다르게 제공할 수도 있습니다.

데이터셋 리소스를 사용한다면 목록과 상세의 항목은 동일하게 제공됩니다.

 

하지만, 상세의 항목이 많거나, 데이터가 큰 경우 목록으로 제공 시 데이터가 너무 커질 수 있습니다. 그래서 목록은 주요 항목만 제공하고 상세 요청 시 모든 항목을 제공해야 할 필요가 있습니다.

 

이 요구사항 처리도 앞에서 살펴본 비지니스 로직 추가로 처리 가능합니다.

 

다음은 예제는 목록과 상세 항목을 다르게 제공하도록 비지니스 로직을 추가한 코드입니다.

목록과 상세 엔드포인트 메소드는 Get과 GetItem 입니다.

SQL 문장을 설정하는 코드를 추가해 원하는 기능을 구현할 수 있었습니다.

procedure TBookRentalResource.Get(const AContext: TEndpointContext;
  const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
const
  SQL_LIST ='SELECT BOOK_SEQ, BOOK_TITLE, BOOK_AUTHOR, BOOK_PRICE FROM BOOK';
begin
  FDQuery1.SQL.Text := SQL_LIST;
  EMSDataSetResource1.List(AContext, ARequest, AResponse);
end;

procedure TBookRentalResource.GetItem(const AContext: TEndpointContext;
  const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
const
  SQL_ITEM_INFO = 'SELECT ' +
                    'BOOK_SEQ, BOOK_TITLE, BOOK_ISBN, BOOK_AUTHOR, BOOK_PRICE, ' +
                    'BOOK_LINK, BOOK_DESCRIPTION ' +
                  'FROM BOOK WHERE BOOK_SEQ = :BOOK_SEQ';
var
  Item: string;
begin
  Item := ARequest.Params.Values['id'];
  FDQuery1.SQL.Text := SQL_ITEM_INFO;
  FDQuery1.ParamByName('BOOK_SEQ').AsString := Item;
  EMSDataSetResource1.Get(AContext, ARequest, AResponse);
end;

RAD 서버로 REST API 개발은 상당히 간편합니다. 패키지로 결과물이 나오기 때문에 배포하기도 편리합니다. 그리고 데이터셋 리소스와 같은 기능을 이용하면 훨씬 더 쉽고 강력하게 개발할 수 있습니다.

직접 구현하다보면 필요한 기능이 있을 것입니다. 앞에서 설명한 비지니스로직 추가하는 방법을 활용해 대부분의 필요 기능을 구현할것이라 생각합니다.

 

추가 정보

 

험프리 험프리.김현수 카테고리 없음 EMS, RAD서버, REST, rest api

FireDAC 자동증가필드 적용 및 갱신

2019. 8. 30. 15:50

대부분 DBMS는 테이블에 자동증가필드를 설정할 수 있습니다.

자동증가필드는 테이블에 새로운 데이터 입력 시 필드의 값을 자동으로 증가해 유효한 값을 갖도록 하는 것이 목적입니다.

 

자동증가필드 설정은, 

SQLServer(MSSQL)은 필드에 IDENTITY 속성을 설정, MySQL은 필드에 auto_increment 속성을 설정해 지원합니다.

오라클과 인터베이스는 필드의 속성을 설정하는 방식을 지원하지 않습니다.

오라클은 Sequence, 인터베이스(그리고 Firebird)는 Generator 생성 후 트리거(Before Insert)와 연동해 자동증가 필드를 적용할 수 있습니다.

이 글에서는, 인터베이스 테이블의 자동증가필드를 설정하고, 테이블에 데이터 추가 후 갱신된 자동증가필드 값을 가져오는 방법을 설명 합니다.

 

다음 코드는 새로운 데이터 추가 후 자동증가필드 값을 가져오는 코드입니다.

코드를 실행하면 2가지 문제를 만나게 됩니다.

procedure TForm1.Button3Click(Sender: TObject);
begin
  FDQuery1.Append;
  FDQuery1.FieldByName('BOOK_TITLE').AsString := 'test';
  FDQuery1.FieldByName('BOOK_AUTHOR').AsString := 'test';
  FDQUery1.Post;

  ShowMessage(IntToStr(FDQuery1.FieldByName('BOOK_SEQ').AsInteger));
end;

 

코드를 실행하면 먼저 다음과 같은 오류를 만나게 됩니다.

BOOK_SEQ 필드는 자동증가필드로, 필드에 값이 없다는 오류입니다. 자동증가필드는 자동으로 값이 채워지도록 설정해야 합니다.

FireDAC 데이터셋 컴포넌트(TFDQuery, TFDTable, TFDStoredProc)에 자동증가필드를 설정하면 해결할 수 있습니다.

  • UpdateOptions > AutoIncFields

자동증가필드 설정 시 새로운 레코드 추가 시 해당 필드에 임의의 음수 값이 자동 설정됩니다.(-1 > -2 > -3 > ...)

 

 

그리고, 다시 실행하면 다음과 같이 음수 값이 표시됩니다. 즉, 자동증가필드값이 갱신되지 않습니다.

인터베이스의 경우 제너레이터로 자동증가필드가 채워져 제너레이터이름을 지정하면, 자동증가필드 값이 갱신됩니다.

  • UpdateOptions > GeneratorName

참고로,

오라클도 시퀀스를 지정해 같은 동작을 할 수 있을 것으로 생각됩니다. 하지만 개발환경이 되지 않아 테스트 해보지 못했습니다.

(오라클 환경이 되시는 분이 계시면 테스트 후 결과 알려주시면 감사하겠습니다.)

 

MySQL, SQLServer 등 자동증가필드를 필드 속성으로 지정하는 경우 위 작업을 별도로 진행하지 않아도 자동 갱신됩니다.

험프리 험프리.김현수 데이터 엑세스 AutoIncField, FireDAC, 자동증가필드

[개발환경] 깃허브 PR을 이용한 코드리뷰 환경 구성(4)

2019. 6. 20. 11:31

이전 글에서는 깃허브에 여러분의 저장소를 만들고 연동하는 내용을 살펴봤습니다.

 

이 글에서는마스터 저장소와 개발용 저장소를 나누고, 깃허브 Pull Request을 이용 코드리뷰 환경을 구성합니다.

이 글에서 다음 내용을 설명합니다.

  1. 코드리뷰를 위한 깃허브 저장소 구성 전략
  2. Fork로 개발용 저장소 복사하기
  3. 변경내역 적용 요청(PR) 및 코드리뷰

 

이 시리즈에서는 깃과 깃허브를 이용하는 기본적인 방법과 깃허브를 이용한 코드리뷰 방법을 설명합니다.

 

코드리뷰를 위한 깃허브 저장소 구성 전략

Pull Request 활용

깃허브에서 코드리뷰는 깃허브의 Pull Request(이하 PR) 기능을 이용합니다.

PR은 원격 저장소간 변경된 내역을 적용(당겨가길) 요청하는 기능입니다.

 

다음 그림은 데브기어 교유과정에서 발생한 PR 예시입니다.

PR 작성 시 작업내용에 대한 코멘트와 여러건의 커밋을 포함합니다.

 

PR의 하단에는 PR에 대한 전반적은 의견을 남길 수 있습니다.

공동작업자(관리자)의 경우 적용요청(PR)을 병합(Merge)하거나 종료(Close)할 수 있습니다.

또한, 커밋 로그에서는 소스코드 줄 단위로 의견을 남기는 것도 가능합니다.

 

Fork - 원격저장소 복사

PR을 활용하려면 원격 저장소가 분리되어 있어야 합니다. 원격 저장소를 분리는 Fork 기능을 이용합니다.

원격 저장소를 분리하면 다음의 잇점이 생깁니다.

  • 마스터(출시용: Release) 저장소와 개발용 저장소가 분리되 관리자와 개발자 작업 공간 분리
  • 여러 개발자(또는 개발팀) 별로 저장소 사용 가능
  • 각 개발자가 작업한 결과물을 관리자 및 팀원들이 검토 후 마스터 저장소에 적용(또는 중단)

 

위 구성을 활용해 다양한 저장소 구성 전략을 새울 수 있습니다.

 

소규모 개발팀에서는 팀장과 운영팀에서 마스터 저장소를 관리합니다. 개발자들은 개인 개발용 저장소에서 작업합니다.

개발작업 완료 후 개발용 저장소에 작업한 내용을 마스터 저장소에 반영요청(PR)합니다. 팀장과 다른 팀원은 PR에 대한 코드리뷰 후 적용 여부를 판단합니다.

 

Fork로 개발용 저장소 복사하기

마스터 저장소를 Fork해 개발용 저장소를 생성하고, 개발용 저장소를 이용하는 방법을 실습합니다.

 

마스터 저장소 다음 링크의 데브기어의 교육용 저장소로 실습을 진행합니다.

 

위 링크 방문 후 상단 우측의 [Fork]를 클릭합니다.

 

잠시후 여러분의 계정에 SetupText 저장소가 생성될 것입니다.

이제 마스터 저장소는 devgeredu/SetupTest로 개발용 저장소는 (여러분 아이디)/SetupTest로 진행합니다.

 

 

이제 앞으로 여러분들은 개발용 저장소에서 작업을 진행해야 합니다.

 

이전 글에서 여러분들이 만든 저장소를 RAD 스튜디오로 불러오고 Commit 및 Push 했던 작업을 개발용 저장소를 대상으로 진행합니다.

 

간단한 과정은

  1. 저장소 URL 복사
  2. RAD 스튜디오에서 File > Open From Version Control...
  3. Git 선택 > 저장소 주소와 저장 경로 지정 후 프로젝트 열기
  4. 프로젝트 소스코드 수정(에디트 컴포넌트 추가)
  5. Project 팬에서 프로젝트 팝업 메뉴 Git > Commit > From Repository Root
  6. 커밋 메시지 입력(Edit 추가) 및 Commit 버튼 클릭
  7. Project 팬에서 프로젝트 팝업 메뉴 Git > Push > From Repository Root
  8. 깃허브 계정으로 로그인
  9. 깃허브 페이지에서 변경내역 확인

 

변경내역 적용 요청(PR) 및 코드리뷰

개발용 저장소에서 개발이 완료(여러차례의 Commit과 Push)되면, 그 내용을 마스터 저장소에 적용해야 합니다.

여러분들은 마스터 저장소에 대한 쓰기 권한이 없기 때문에 마스터 저장소의 작업자에게 내가 작성한 코드를 적용하길 요청하는 Pull Request를 작성해야 합니다.

 

개발용 저장소 페이지의 상단에 Pull requests 탭을 선택합니다.

그리고, [New pull request] 버튼을 클릭합니다.

 

페이지가 이동되면 마스터 저장소로 이동되고, PR을 생성할 수 있습니다.

먼저 두개의 브랜치를 대상을 확인합니다. 커밋 로그를 확인합니다. 하단에 변경된 파일 정보를 확인할 수 있습니다.

확인 후 [Create pull request] 버튼을 클릭해 PR을 생성합니다.

PR의 제목과 내용 입력 후 [Create pull request] 버튼을 클릭합니다.

 

다음과 같이 PR이 등록되었습니다. 

PR 상세화면에서는 추가 코멘트를 달거나, 본인이 작성한 PR을 닫을 수 있습니다.

 

Pull Requests 탭을 다시 선택하면 PR 목록이 표시됩니다.

이 목록은 마스터 저장소 관리자 뿐아니라 누구에게나 공개됩니다.

 

마스터 저장소 관리자의 PR 상세 페이지에는 PR에 대한 정보와 함께, 변경사항을 병합(Merge)가능합니다.

관리자는 다른 코드리뷰어의 PR에 대한 코멘트와 소스코드에 대한 코멘트를 참고해서 병합 여부를 판단할 수 있습니다.

 

PR의 변경사항 병합을 결정했다면, [Merge pull request] 버튼을 클릭해 병합하면, 해당 PR의 상태가 Open에서 Merged로 변경되어 닫힙니다.

소스코드를 확인해보면 PR에서 변경된 "Edit 추가" 커밋 메시지가 표시됨을 확인할 수 있습니다.

 

정리

깃 설치부터 RAD 스튜디오의 깃 설정, 생성한 저자소와 연동 및 코드리뷰를 위한 저장소 방안까지 다음 시리즈를 통해 살펴봤습니다.

 

 

 

 

사실 이 글은 가장 기본적인 내용으로만 구성했습니다. 깃과 깃허브에는 더 다양한 기능들이 있습니다.

여러분들의 조직과 환경에 맞춰 그 기능을 추가 학습해 개발조직에 가장 중요한 소스코드를 효과적으로 관리 운영하시기 바랍니다.

 

험프리 험프리.김현수 Delphi/C++Builder fork, git, github, PR, pull request, RAD스튜디오, , 깃허브, 저장소구성, 코드리뷰

[개발환경] 깃허브에 저장소 생성 및 연동하기(3)

2019. 6. 19. 17:37

이전 글에서 RAD 스튜디오에서 Git 설정 및 불러오기를 진행했습니다.

 

이 글에서는 깃허브에 여러분의 저장소를 생성하고, RAD 스튜디오로 열고, 변경사항을 저장소에 반영하는 내용을 진행합니다.

이 글에서는 다음 내용을 설명합니다.

  1. 깃허브에 저장소 생성
  2. RAD 스튜디오에서 생성한 저장소 열기
  3. 소스코드 변경 후 커밋 및 저장소에 반영

 

이 시리즈에서는 깃과 깃허브를 이용하는 기본적인 방법과 깃허브를 이용한 코드리뷰 방법을 설명합니다.

 

깃허브에 저장소 생성

깃허브에 여러분의 저장소를 생성하는 내용을 설명합니다.

 

깃허브에서 새로운 저장소를 생성합니다.

저장소 이름을 입력하고, .gitignore를 선택합니다.(.gitignore는 버전관리 제외 대상 포맷 파일입니다.)

[Create repository] 버튼을 눌러 저장소를 생성합니다.

생성된 저장소에서 [Clone or download] 버튼을 누르고, 클립보드로 복사 버튼을 클릭합니다.

 

RAD 스튜디오에서 생성한 저장소 열기

RAD 스튜디오를 열고, File > Open From Version Controll... 메뉴를 선택합니다.

Git 선택 후 [OK] 버튼을 클릭합니다.

Source 항목은 깃 허브에서 클립보드로 복사한 주소를 붙여넣기(Ctrl + V) 후 Destination 항목에 저장할 로컬 경로 선택 후 [OK] 버튼을 클릭합니다.

 

소스코드 변경 후 커밋 및 저장소 반영

위에서 불러온 저장소에 프로젝트 파일을 만들고 커밋하는 절차를 설명합니다.

 

프로젝트 생성 및 저장

위의 로컬 경로에 프로젝트 파일을 저장합니다.

File > New > WIndows VCL Application

File > Savle All

 

이제 프로젝트 팬에서 프로젝트 팝업 메뉴 중 Git 메뉴가 표시됩니다.
(프로젝트 파일의 디렉토리에 깃 정보가 있으면 Git 메뉴 표시)

 

Commit - 로컬 저장소에 소스코드 반영

Git > Commit > From Repository Root 메뉴를 선택합니다.

Commit 화면에서 [Show unversioned files] 체크박스를 선택합니다.

파일 중 커밋할 대상을 선택합니다.(Check or uncheck all 선택)

커밋 메시지(Comment)를 입력 합니다.

[Commit] 버튼을 클릭합니다.

Push - 원격 저장소에 로컬저장소의 내용 반영

Git > Push > From Repository Root 메뉴를 선택합니다.

Git Login 창에서 깃허브의 계정으로 로그인합니다.

원격 저장소 확인

깃허브에서 여러분의 저장소에 반영된 목록을 확인합니다.

이후 작업

소스코드를 수정하고, 커밋(Commit)을 합니다. 커밋된 변경내역을 원격저장소에 반영이 필요한 경우 푸시(Push)합니다.

 

커밋 작업은 자주하는 것이 좋지만, 의미있는 단위(작은 기능 추가, 함수 추가, 코드 변경 등)로 커밋하는 것이 좋습니다.

향후 커밋된 작업으로 소스코드를 되돌려야 할 수 있습니다.

 

참고자료

다음 링크에서 협업을 위한 깃허브 구성과 코드리뷰 환경 구성하는 내용을 확인할 수 있습니다.

험프리 험프리.김현수 Delphi/C++Builder commit, git, github, , 깃허브

[개발환경] RAD 스튜디오에서 Git 설정 및 불러오기(2)

2019. 6. 19. 16:21

이전 글에서 깃을 설치하고, 저장소의 구성을 살펴봤습니다.

 

이 글에서는 RAD 스튜디오에서 깃을 설정하고 깃허브 소스코드를 RAD 스튜디오에서 여는 방법을 설명합니다.

 

이 글에서는 다음 내용을 설명합니다.

  1. RAD 스튜디오에서 깃 설정
  2. RAD 스튜디오에서 깃허브 저장소의 소스코드 열기
  3. 써드파티 깃 클라이언트 소개

 

이 시리즈에서는 깃과 깃허브를 이용하는 기본적인 방법과 깃허브를 이용한 코드리뷰 방법을 설명합니다.

 

RAD 스튜디오에서 깃 설정

RAD 스튜디오(델파이, C++빌더) 10.3 기준으로 설명합니다. 다른 버전의 경우 옵션 등의 경로가 다를 수 있습니다.

 

툴 옵션(Tools > Options...) 표시 후 Version Control > Git 화면을 표시합니다.

 - Git Executable : 깃 설치 프로그램을 설치한 경로 하위의 bin\git.exe 파일을 선택합니다.

 - Authorization(User Name, Email) : 소스코드 저장 시 적용할 이름과 이메일을 기록합니다.

 

깃허브의 소스코드 열기

깃허브에 저장된 소스코드를 RAD 스튜디오로 여는 과정을 설명합니다.

 

다음 링크는 데브기어 교육용 저장소입니다. 이 저장소의 소스코드를 통해 설명합니다.

(여러분들은 이 저장소의 쓰기권한이 없으므로, 소스코드를 가져올 수 있지만 저장할 수는 없습니다.

가져온 소스를 저장하려면 저장소를 생성하거나 복사(Fork)한 저장소여야 합니다.)

 

다음 링크를 방문합니다.

페이지 중간 우측의 [Clone or download] 버튼 클릭 후 클립보드로 복사 버튼을 클릭합니다.

 

RAD 스튜디오를 열고, File > Open From Version Control... 메뉴를 선택합니다.

Git 선택 후 [OK] 버튼을 클릭합니다.

Source 항목은 깃 허브에서 클립보드로 복사한 주소를 붙여넣기(Ctrl + V) 후 Destination 항목에 저장할 로컬 경로 선택 후 [OK] 버튼을 클릭합니다.

로컬로 복사(Clone)가 완료되면 [OK] 버튼 클릭. 지정된 경로에 소스코드가 복사되었습니다.

 

RAD 스튜디오에 프로젝트를 열수 있는 화면이 표시되면, 프로젝트를 선택 후 [OK] 버튼을 클릭해 프로젝트를 엽니다.

 

이후, 프로젝트 패널에서 프로젝트 팝업 메뉴 중 Git 메뉴를 통해 "Commit, Pull, Push" 등의 작업이 가능합니다.

 

다음 글에서는 위 기능을 이용해 변경(추가/수정/삭제)된 소스코드를 저장소에 반영하는 기능등을 살펴봅니다.

 

써드파티 깃 클라이언트로 소개

RAD 스튜디오의 깃 연동은 가장 중요한 기능(Commit, Pull, Push, Clean, Show Log) 위주로 제공합니다.

깃에는 위 기능 외에 매우 많은 기능(Branch, Merge, Tag 등)을 명령어로 제공합니다. 

 

이 기능들을 UI기반으로 사용할 수 있는 써드파티 깃 클라이언트를 이용할 수 있습니다.

많은 써드파티 깃 클라이언트가 있지만, 이 글에서는 SourceTree와 TortoiseGit을 소개합니다.

 

SourceTree

SourceTree는 저장소의 내용을 시각적으로 표현하고 관리할 수 있는 깃 GUI입니다.

현재 가장 인기있는 깃 클라이언트 중 하나입니다.

TortoiseGit

탐색기 기반으로 동작하는 깃 클라이언트입니다.

개인적으로 예전부터 사용하던 TortoiseSVN이 익숙해 사용하고 있습니다.

참고자료

다음 링크에서 깃허브에 저장소를 생성하고 연동(불러오기, 저장히기)하는 내용을 확인할 수 있습니다.

 

참고링크

 

험프리 험프리.김현수 Delphi/C++Builder git, github, RAD스튜디오, , 깃허브

[개발환경] Git 설치와 저장소 구성(1)

2019. 6. 18. 17:59

깃(Git)은 컴퓨터 파일의 변경사항을 추적하고 여러명의 사용자들 간에 해당 파일들의 작업을 조율하기 위한 분산 버전 관리 시스템이다. - 위키백과

깃허브(Github)는 분산 버전관리 툴인 깃을 사용하는 프로젝트를 지원하는 웹호스팅 서비스입니다. - 위키백과

 

버전관리 시스템은 소스코드의 중요한 변화를 기록하는 작업으로 개인 및 팀 작업에 반드시 필요한 요소입니다.

깃허브는 주로 오픈소스등 공개된 저장소로 사용되지만, 개인용 저장소를 생성해 비공개 저장소로 이용가능합니다.

 

이 시리즈에서는 깃과 깃허브를 이용하는 기본적인 방법과 깃허브를 이용한 코드리뷰 방법을 설명합니다.

 

이 글에서는 다음 내용을 설명합니다.

  1. 깃 설치하기
  2. 깃 저장소와 소스코드의 구성
  3. 기타 - 깃의 기능(명령어) 소개

깃 설치

깃의 명령어를 사용하기 위해 깃 설치 프로그램을 설치합니다.

(깃허브 계정이 없다면)앞으로 깃허브에서의 실습을 위해 깃허브 계정을 생성합니다.

깃 저장소와 소스코드의 구성

기본 구성

깃 저장소는 위 그림과 같이 원격(Remote-Github) 저장소와 로컬(Local) 저장소로 분산되어 운용됩니다.

 

깃 허브에서 저장소를 생성 후, 로컬에서 Clone 명령을 통해 로컬로 소스코드를 복사합니다.

이후 변경된 내용은 Pull 명령을 통해 로컬에 반영합니다.

로컬의 소스코드를 편집 후 로컬 저장소에 Commit해 반영합니다.

로컬 저장소의 변경된 내역은 Push 명령을 통해 원격 저장소에 반영합니다.

 

마스터, 개발용 저장소 구성

위 그림과 같이 마스터 저장소(Master Rep.)와 개발용 저장소(Dev. Rep.)를 분리해 운영할 수 있습니다.

 

마스터 저장소를 Fork해 개발용 저장소로 복사해 병행 운용합니다.

마스터 저장소는 관리자가 운용, 개발용 저장소는 개발자가 운용합니다.

개발자는 개발용 저장소에서 소스코드를 Clone해 개발을 진행합니다.

개발용 저장소의 변경된 내역은 Pull Request로 마스터 저장소에 적용을 요청합니다.

관리자는 요청(Pull Request)을 확인 후 마스터 저장소에 적용합니다.

 

Fork는 깃 허브에서 제공하는 기능입니다. 또한 깃 허브에서는 Pull Request에시 커멘트를 추가, 답변할 수 있는 기능을 제공합니다. 이 기능을 이용해 코드리뷰 가능합니다.

 

기타 - 깃의 기능(명령어) 소개

깃은 명령줄 도구로 명령어를 통해 제어할 수 있습니다. 개발 도구나 써드파티 깃 클라이언트는 이 명령어 기반으로 UI를 제공합니다.

 

이 시리즈에서 사용하는 기능 들

  • Commit - 소스코드(및 기타 파일)의 변경(추가,수정,삭제) 내역을 로컬 저장소에 적용
  • Clone - 원격 저장소의 파일을 로컬로 복사해 로컬 저장소 생성
  • Pull - 원격 저장소의 변경된 내역을 로컬 저장소에 반영
  • Push - 로컬 저장소의 변경된 내역을 원격 저장소에 반영
  • Fork - 타인의 원격 저장소를 내 소유의 원격 저장소로 복사
  • Pull Request - Fork한 저장소의 변경내역을 소유자의 저장소에 적용을 요청

기타 자주사용하는 기능 들

  • Branch - 소스코드를 독립적으로 작업 진행하기 위한 분계(가지치기)
  • Checkout - Branch한 소스코드로 전환
  • Merge - Branch한 소스코드를 병합
  • Tag - 특정 시점에 알기 쉬운 이름을 부여(보통 릴리즈 시 사용. v1.0 등)

기타 다른 명령어 및 기능은 깃의 참고문서를 참고하시기 바랍니다.

 

참고 자료

다음 링크에서 RAD 스튜디오에서 Git 설정 및 불러오기 내용을 확인할 수 있습니다.

 

깃에 대해 추가 학습을 원한다면 다음 링크를 참고하시기 바랍니다.

 

험프리 험프리.김현수 Delphi/C++Builder git, github, SCM, , 깃허브

[FMX] RAD 스튜디오 10.3.1에서 FCM 전송 설정하기

2019. 6. 10. 14:13

이 글은 Marco Cantu가 작성한 Firebase Android Push Notification Support with RAD Studio 10.3.1의 의역입니다.

 

구글은 다음과 같이 발표했습니다.  "GCM 서버와 클라이언트 API는 지원중단하며, 2019년 5월 29일에 삭제될 예정입니다. 안정적이고 확장가능한 GCM 인프라 및 많은 새로운 기능을 계승한 Firebase Cloud Messaging(FCM)으로 GCM 애플리케이션을 전환하십시오."

 

앞으로 파이어몽키 안드로이드 앱에서 푸시 알림 지원을 사용하려면, 구글의 Firebase를 사용해야 합니다. 이 글에서는 Delphi, C++Builder 및 RAD 스튜디오 10.3.1을 사용해 파이어몽키 안드로이드 앱에서 Firebase 푸시 알림 지원하는 과정을 소개합니다.

 

사전준비 - 안드로이드 푸시 알림 패치

시작에 앞서 IDE의 겟잇 패키지 매니저(Tools > Getit Package Manager...)에서 "Android Push Notification Patch 1.0"을 찾아 설치 버튼을 클릭 해 다운로드 합니다.("Android"로 검색)

 

최신 로드맵에서 소개했듯이 향후 10.3.2 릴리즈에서 이 지원을 더욱 간소화(RAD 서버의 Android Firebase 푸시 알림 지원 추가) 및 10.4의 Firebase 및 기타 관련 서비스의 전체 통합 지원 제공을 계획하고 있습니다.

 

Firebase에서 애플리케이션을 활성화하려면, 다음 3단계로 작업을 진행해야 합니다.

 

1, Firebase 프로젝트를 만들고 파이어몽키 프로젝트를 Google Firebase 콘솔에 등록

2, 파이어몽키 프로젝트를 새로 만들거나 기존의 프로젝트에 RAD 스튜디오 10.3.1 푸시 알림 구성

3, GCM(Google Cloud Messaging)  대신 Firebase를 지원하도록 파이어몽키 프로젝트 변경

 

Firebase 프로젝트 생성 및 Firebase 콘솔에 파이어몽키 프로젝트 등록

1, Google Firebase 콘솔(https://console.firebase.google.com/)에 접속 후 [새 프로젝트]를 클릭 합니다.

2, 필요한 사항을 설정하고 [프로젝트 만들기] 버튼을 클릭합니다.

 - 프로젝트 이름은 임의로 설정(예> FirebaseApp)

 - Cloud Firestore 위치는 현재(2019.05) 가장 가까운 asia-northeast1 추천

3, 프로젝트 생성 후 Project Overview 옆의 톱니바퀴 아이콘 클릭 후 [프로젝트 설정] 메뉴를 선택해 프로젝트 설정 화면으로 이동합니다.

4, 안드로이드 아이콘을 클릭하여 "Android 앱에 Firebase 추가 화면"으로 이동합니다.

 

5, 앱 등록 단계에서 Android 패키지 이름을 지정 후 [앱 등록] 버튼을 클릭합니다.

 - 파이어몽키 앱의 기본 패키지 이름은 com.embarcadero.프로젝트이름 입니다.

 - 파이어몽키 프로젝트를 FirebaseApp으로 생성 시 com.embarcadero.FirebaseApp으로 생성합니다.

 - 프로젝트 옵션(Application > Version Info의 package 항목)에서 패키지 이름 설정 가능합니다.

6, 구성 파일(google-services.json)을 다운로드 합니다. 

다운로드 후 [다음] 버튼 클릭 후 이후 단계는 건너 뛰기를 선택합니다.

 

7, 안드로이드 문자열 리소스 파일(strings.xml)을 편집합니다.

 - strings.xml 파일은 겟잇 패키지 매니저에서 다운로드한 파일에 포함되어 있습니다.
    (C:\Users\Public\Documents\Embarcadero\Studio\20.0\CatalogRepository\AndroidPushNotificationsPatch-1.0\FireBase)

 - strings.xml 파일을 파이어몽키 프로젝트 경로에 복사합니다.(예> C:\MyFirebaseApplication 등)

 - 복사한 strings.xml 파일을 텍스트 에디터 등으로 열어 다음을 수정합니다.

  * google_app_id와 gcm_defaultSenderId, fcm_fallback_notification_channel_label 항목만 두고 나머지 string 태그 삭제

  * google_app_id = mobilesdk_app_id

  * gcm_defaultSenderId = project_number
    (6단계에서 다운로드 받은 google-services.json 파일을 참조)

 

파이어몽키 프로젝트에 푸시 알림 구성

8, 파이어몽키 프로젝트를 생성(또는 기존 프로젝트 오픈) 합니다.

 

9, Firebase 콘솔에 등록된 프로젝트 이름과 일치하도록 FMX 프로젝트 이름을 변경합니다.

    (이 예제에서는 FirebaseApp으로 설정합니다.)

7단계에서 업데이트 한 strings.xml 파일과 동일한 경로에 프로젝트를 저장합니다.(예> C:\MyFirebaseApplication)

저장 후 안드로이드 플랫폼 선택 후 빌드를 실행해 해당 경로에 AndroidManifest.template.xml을 생성합니다. 이 파일은 마지막 단계에서 편집합니다.

 

10, Firebase 이벤트 로그를 표시하기 위해 TMemo 컴포넌트를 폼에 추가하고, MemoLog로 이름을 변경합니다.

다음 Form의 OnCreate 이벤트를 추가해 푸시 알림 서비스 초기화 및 연결을 위한 코드를 구현합니다.

 

이 코드는 겟잇 패키지 매니저에 포함되어 있는 Snippets.txt 파일을 참조하기 바랍니다.

Snippets.txt 파일에는 푸시 알림 서비스로 연결을 만드는 프로세스와 이벤트에 대한 코드가 포함되어 있습니다.

 

푸시 알림 서비스와 연결을 위한 코드를 OnCreate 이벤트에 복사합니다.

Snippets.txt의 1~2줄을 implimentation 아래에 추가합니다.

또한, interface uses 절에 System.PushNotification을 추가합니다.

이 서비스로 Firebase와 연결 후 장치아이디와 장치 토큰을 수신하게 됩니다. 필요한 변수와 이벤트를 private 영역에 선언합니다.

두개의 변수는 장치 아이디와 장치 토큰을 수신하는데 사용합니다.

OnServiceConnectionChange 이벤트는 연결 변경 시 장치 토큰을 얻는데 사용합니다.

OnReceiveNotificationEvent 이벤트는 푸시를 받기 위해 사용됩니다.

 

Snippets.txt를 참고해 2개의 이벤트를 구현합니다.

GCM 대신 FCM을 지원하도록 프로젝트 변경

11, IDE 오른쪽의 프로젝트 에서 Android > Libraries를 확장하고, 다음의 라이브러리를 각각 마우스 오른쪽 버튼을 이용해 수동으로 해제합니다.

  • cloud-messaging.dex.jar
  • google-analytics-v2.dex.jar
  • google-play로 시작하는 모든 Google Play 라이브러리

 

12, 프로젝트의 Libraries에 마우스 오른쪽 버튼을 누르고, [Add] 메뉴를 선택 해 Firebase 라이브러리들과 업데이트된 Google Play Services 라이브러리를 추가합니다.

(이 라이브러리 파일들은 겟잇 패키지 매니저에서 다운로드받은 파일들 중 jars 디렉토리에 있습니다.)

 

13, 다음 Firebase 지원을 위한 AndroidAPI.JNI.Firebase.pas와 업데이트된 FMX.PushNotification.Android.pas 파일을 프로젝트 경로에 복사 후 추가합니다.

(이 파일들은 겟잇 패키지 매니저에서 다운로드 받은 파일 중에 있습니다.)

 

14, IDE 메인 메뉴 중 Project > Deployment로 배포화면을 표시 후 프로젝트 경로의 strings.xml 파일을 추가합니다.

 

또한 strings.xml 파일의 Remote Path를 "res\Values\"로 업데이트 후 변경사항을 저장합니다.

 

15, 마지막 단계로 AndroidManifest.template.xml 파일을 변경합니다.(9단계에서 안드로이드 타겟인 프로젝트를 빌드해 생성)

 

Snippets.txt 파일 하단의 XML 구문을 복사해 <%receivers%>아래(</application> 태그 바로위)에 붙여넣기 합니다.

Firebase Clound Messaging 전송 테스트

16, FMX 앱을 안드로이드 장비에 배포합니다. 실행 시 앱은 자동으로 Firebase에 등록되고, 로그에 디바이스 토큰이 표시됩니다.

이 장비에 푸시메시지를 전송하려면 Firebase 토큰이 필요합니다. 로그에 표시된 토큰을 복사합니다.

 

17, 브라우저에서 Google Firebase 콘솔(https://console.firebase.google.com/) 접속 후 앞에서 만든 프로젝트를 선택합니다.

사이드 메뉴의 "성장 > Cloud Messaging" 메뉴를 선택 후 [Send your first message] 버튼을 클릭합니다.

 

18, 알림의 제목과 텍스트를 입력하고, [테스트 메시지 전송] 버튼을 클릭합니다.

16에서 선택한 Firebase 토큰을 "FCM 등록 토큰 추가" 항목에 입력 후 (+) 버튼 클릭해 토큰을 추가합니다.

[테스트] 버튼을 눌러 메시지를 전송합니다.

다음 화면과 같이 전송한 메시지가 안드로이드 화면에 표시되는 것을 확인할 수 있습니다. 또한 메시지는 안드로이드 알림센터에도 표시됩니다.

험프리 험프리.김현수 카테고리 없음 FCM, FireBase Cloud Messaging, FMX, PushNotification, Push알림, 안드로이드 푸시

  1. Blog Icon
    이윤희

    10.3.1의 Firebase Android Push Notification은 iOS 지원이 안되는거죠??

  2. Blog Icon
    이경백

    설명 감사합니다.
    User(고객사의 관리자)가 Mobile Users들에게 Message를 Send하려면
    VCL 프로젝트에서 FCM App으로 Message를 Send하는 방법은 없나요?
    매번 17번 18번처럼 개발자의 Google console에 들어와서 할수는 없을 텐데요.
    GCM에서는 TBackendPush.PushData()로 구현했습니다.

델파이 코드 주석 추가 및 문서화 방안

2019. 5. 30. 17:09

주석은 코드를 더 읽기 쉽고, 유지보수하기 쉽게 할 수 있는 가장 기본적인 요소 중 하나입니다.

특히 팀단위로 개발하거나, 오랫동안 유지보수해야 하는 경우 진가를 발휘합니다.

 

이 글에서는 주석을 좀 더 효과적으로 달고, 내용을 문서화하는 방법을 소개합니다.

XMLDoc과 JavaDoc 주석의 특징과 추가 방법, 문서화 하는 방법을 알아봅니다.

 

XMLDoc과 JavaDoc 주석 추가

XMLDoc 주석

XMLDoc 주석의 특징

  • 3 중 슬래시(///)로 시작
  • XML 태그로 작성
  • 코드 에디터의 헬프 인사이트에 표시
  • XML 태그로 가독성이 다소 떨어짐

XML 주요 항목

  • <summary> 함수 또는 클래스에 대한 설명
  • <param name="파라메터 이름"> 파라메터에 대한 설명
  • <returns> 함수의 반환 값 설명
  • < exception cref="예외 유형"> 메소드에서 전달되는 예외

예시

XMLDoc의 특징

XML Doc 주석 추가 시 코드 에디터의 헬프 인사이트에 표시됩니다.

단, 선언부(interface 영역)에 XML Doc 추가 시에만 표시됩니다.

한가지 팁으로, 코드 템플릿에 템플릿을 추가하면 필요시 코드에디터에 주석을 쉽게 추가 할 수 있습니다.

(Ctrl + J > 키워드 입력 또는 키워드 입력 > Ctrl + J)

 

코드 템플릿 추가방법

View > Tool Windows > Templates 메뉴 선택

New Code Template 버튼 클릭

아래 코드 참고해 XML 태그 수정 후 저장(예> xmldoc.summary.xml)

xmldoc.summary.xml
0.00MB

 

또는 위 XML 태그로 xml 파일 생성(별도의 텍스트 에디터에서) 및 저장

템플릿 디렉토리에 xml 파일 복사

(10.3 기준 템플릿 디렉토리 : C:\Program Files (x86)\Embarcadero\Studio\20.0\ObjRepos\en\Code_Templates\Delphi)

 

JavaDoc 주석

자바의 주석 형식으로 주석을 작성하면, 별도 프로그램을 이용 문서화(HTML, CHM, PDF 등)가 가능합니다.

JavaDoc 주석의 특징

  • 블록 주성({** 시작 *}로 끝) 또는 한줄 주석(3 중 슬래시(///)로 시작)
  • 주석의 속성(태그)은 @으로 시작
  • XMLDoc에 비해 주석 가독성이 높음(짧음)

JavaDoc 주요 태그

  • 태그 없는 경우 함수에 대한 설명
  • @author 작성자
  • @version 버전
  • @param 메소드 파라메터 설명
  • @return 반환 값 설명
  • @throws 메소드에서 전달되는 예외

예시

 

주석 문서화

추가한 주석을 문서화(문서로 내보내기)하는 방법을 알아봅니다.

XMLDoc 주석 문서화

XMLDoc을 문서화 하기 위해서는 Model을 생성해야 합니다.

Projects 팬에서 Model View 탭을 선택 후 모델을 생성합니다.

Model View에서 문서화할 대상(프로젝트 또는 유닛) 선택 후 팝업메뉴에서 "Generate Documentation..." 메뉴를 선택 합니다.

문서화 범위(Scope) 및 옵션(Options)를 선택 후 [OK] 버튼을 클릭 합니다.

 

결과 예시

다음과 같이 XMLDoc 주석을 추가했습니다.

 

생성된 HTML은 다음과 같습니다.

JavaDoc 주석 문서화

JavaDoc 포맷의 주석을 문서화 하기 위해서는 별도의 프로그램을 이용해야 합니다.

이 글에서는 DelphiCodeToDoc를 이용해 문서화합니다.

 

DelphiCodeToDoc은 델파이로 제작된 오픈소스(GPL)로 자유롭게 설치하고 필요한 기능을 수정해 기여할 수 있습니다.

문서화 포맷으로 CHM, HTML, PDF를 지원합니다.(Configuration 화면의 Output options > Output Format에서 지정)

결과물이 썩 이쁘지는 않지만, 그렇게 나쁘지도 않습니다.

 

DelphiCodeToDoc(DCTD) 설치

DelphiCodeToDoc(DCTD) 실행

DelphiCodeToDoc.exe 실행 후 [New] 버튼 클릭(또는 File > New 메뉴 선택)

마법사 페이지에서 옵션 설정 및 대상 파일(또는 디렉토리) 선택 및 추가 후 [Finish]

메인화면에서 [Check and Build] 버튼 클릭(또는 Project > Check and Build 메뉴 선택)

빌드 시 CHM 도움말이 표시되고, 지정된 경로(Ouput Folder)에 지정된 포맷

 

결과 예시

DCTD에서 설정한 프로젝트 관련 내용 표시

프로젝트 이름을 한글로 설정 시 좌측 내용트리에 한글깨짐 현상이 있으나, 문서내에서는 정상출력 확인

 

프로시저에 대한 문서

@author, @version 태그 추가했지만, 문서에 표시되지 않음

웹으로 내보내기한 Unit에 대한 문서

총평

XMLDoc과 JavaDoc 주석을 추가하고 문서화 하는 내용을 살펴봤습니다.

 

XMLDoc은 주석에 XML 태그가 있어 코드내의 가독성은 떨어지지만, 헬프 인사이트에 표시된다는 장점이 있습니다.(단, 선언부에 주석을 달아야 합니다.

JavaDoc은 코드내 주석에 대한 가독성이 상대적으로 높지만, 헬프 인사이트 등으로 표시되지 않습니다.

 

문서의 결과물은 개인적으로 JavaDoc의 결과물이 더 다양하고 깔끔하다고 생각합니다.

또한 오픈소스여서 필요한 경우 직접 수정해 필요한 기능을 확장할 수 있습니다.(물론 쉽지 않습니다.)

 

위 내용을 종합적으로 판단한다면 저는 개인적으로 JavaDoc을 선호합니다. 이유는 XMLDoc은 선언부에 주석을 달아야 하기 때문에 구현부를 전체적으로 살펴볼때 JavaDoc의 주석 형식이 더 유용할 것으로 판단됩니다.

(또는 2개의 방식을 번갈아가며 사용해도 좋을 것 같습니다. 선언부는 XMLDoc, 구현부는 JavaDoc)

 

여러분들도 여러분들의 환경에 맞는 방식을 선택 해보시기 바랍니다.

 

참고로 위 방법 외에도 doxygen 등과 같은 주석 문서화하는 방법이 많습니다.

그리고 JavaDoc 주석을 문서화 하는 프로그램도 많을 것입니다.

혹시 아는 주석 문서화 방법이나 문서화 프로그램이 있다면 댓글로 알려주시면 감사하겠습니다.

 

참고 링크

험프리 험프리.김현수 Delphi/C++Builder DCTD, DelphiCodeToDoc, Javadoc, XMLDoc, 주석