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

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.02 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