Jump to content
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr ×
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr

샘플을 통해 알아보는, 화면(UI) 요소와 비화면(비즈니스 로직, 데이터베이스 액세스 등) 요소 분리


Recommended Posts

목차


화면(UI) 요소와 비화면(비즈니스 로직, 데이터베이스 액세스 등) 요소를 분리해야 하는 이유와 중요성

델파이는 처음 출시될 때부터, 데이터베이스와 연결하는 애플리케이션을 개발하는 도구라는 면에서 탁월했다 (델파이라는 이름이 채택된 이유).

또한, 델파이는 목적에 따라 유닛을 나누어 개발하기도 좋다. 델파이에서 각 유닛은 별도의 소스 파일에 저장된다. 그리고 어느 유닛이든 자신에게 필요한 다른 유닛을 사용(참조)할 수 있다. 그저 uses 절에 다른 유닛(Unit)을 적어주고 나면, 그 유닛 파일(*.pas)의 공개된 (public 등) 클래스 매소드나 객체의 데이터를 호출하여 사용할 수 있다.

데이터베이스 애플리케이션을 만들 때, 개발자는 먼저 애플리케이션의 구조를 잡게 된다. 이때, 개발자는  화면 (UI, User Interface) 요소와 비화면(비즈니스 로직, 데이터베이스 액세스 등) 요소를 각각 별도의 유닛으로 구분해 놓는 것과 구분하지 않고 하나의 유닛 안에 모두 섞어 두는 것 모두 가능하다. 즉, 크게 이렇게 볼 수 있다.

  1. 유닛 파일 1개 안에 UI 요소와 비UI 요소가 모두 섞여 들어 있는 구조
  2. UI를 담당하는 유닛 파일과 비UI 요소를 담고 있는 유닛 파일을 각각 따로 만드는 구조

1번 구조는 당장 편하게 만들 수 있다는 장점이 있지만, 유닛의 유연성과 재사용성이 없다. 따라서 애플리케이션이 커질 수록, 같은 코드가 중복되고 관리하기가 어려워진다.

2번 구조는 각 유닛 마다 담당 역할이 있고, 서로 돕게 되므로 유연성과 재사용성이 좋다.예를 들어 Form 화면을 표현하는 유닛은 데이터 처리를 담당하는 유닛을 불러서 사용한다. 이때, UI 요소는 비 UI 요소에 의존성을 가지지만, 비 UI 요소는UI 요소에 의존성을 가지면 안된다는 원칙이 중요하다. 

예를 들어, Form 유닛에서 데이터 모듈(Data Module) 유닛을 포함해서 사용한다.
Uses
  Data1, Data2, Data3.....

의존성 단방향 원칙을 지키면 유닛 의존성 순환(UDCs, Unit Dependency Cycles) 문제를 방지할 수 있다. (델파이와 유닛 의존에 대한 깊이 있는 논의는 다음 기회에 살펴보기로 하자). 

 

주의할 점: UI 요소와 데이터 요소를 분리할 때 

데이터 모듈(Data Module) 유닛 안에 있는 uses 절에 Form 유닛(들)이 들어 있으면 위 규칙 위반이다!. 아쉽지만 실제 현장에서는 이런 경우가 꽤 있다. 즉 데이터 모듈에서 특정 Form 화면 유닛에서 공개하고 있는 메소드 등을 사용하기 위해 참조(의존)하는 것인데, 앞에서 설명한 것처럼 그러면 단방향 의존 원칙이 깨지고 유닛 참조 순환 문제가 생긴다 (문제에 대한 설명은 다음 기회에).

이런 유닛 참조 순환은 컴파일과 실행 시 성능을 떨어 뜨릴 뿐만 아니라 코드가 복잡해지고, 유지 보수도 어려워진다. 예를 들면, 반대 방향 의존 즉, 데이터 처리 로직 안에서 다른 Form 유닛 안에 있는 컨트롤을 참조해서 처리해야 한다면, 데이터 처리 담당 개발자는 해당 Form 유닛 담당 개발자와 따로 협의를 해야 한다.

 

샘플 프로젝트: 구성

이 샘플에서 사용한 UI 프레임워크는 FMX이며, 데이터베이스는 SQLite이다. 이 샘플은 데이터 처리를 데이터 모듈 유닛에서만 담당하도록 구성되어 있다. 따라서, 다양한 플랫폼 용으로 다양한 화면을 추가하더라도 각 화면에서는 필요한 데이터 모듈을 재사용하면 된다.

데이터베이스 관련 유닛을 분리하는 아주 쉽고 좋은 방법은 델파이에서 기본 제공하는 Data Module Unit을 사용하는 것이다. File > New 에서 아래와 같이 Database 의 Data Module 을 선택하게 되면 새로운 유닛이 만들어 진다. 이 유닛은 실제로 화면은 존재 하지 않는 컨테이너이며, 여기에는 UI 컴포넌트가 아닌 데이터 관련 비시각적 컴포넌트를 올려놓고 사용 할 수 있다. 

image.png

샘플프로젝트에서는 FireDAC 의 TFDCConnection 과 TFDQuery 를 사용하였다.

image.png

메인폼은 조회한 데이터를 화면에서 볼 수 있는 TListView를 사용 하였다.

image.png

 

샘플프로젝트: 데이터모듈 Unit

조회한 데이터를 메인폼등에서 가져다 쓰기 위해서 데이터를 전달하는 방법은 여러가지가 있지만 이곳 샘플에서는 레코드의 TStringList 변수에 할당 하였다. 데이터 필드가 많을 때는 조회한 결과를 TFDMemTable에 저장하여 메인폼에서 사용하는것도 좋은 방법이 된다. TFDMemTable은 데이터 필드를 동적으로 정의 할 수가 있어서 조회한 데이터 개수나 형태에 따라 지정 하여 사용 하면 된다.

(TFDMemTable 관련 글 보기)

unit DBModule;

interface

uses
  System.SysUtils, System.Classes, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf,
  FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Phys.SQLite, FireDAC.Phys.SQLiteDef,
  FireDAC.Stan.ExprFuncs, FireDAC.Phys.SQLiteWrapper.Stat, FireDAC.FMXUI.Wait, FireDAC.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf,
  FireDAC.DApt, Data.DB, FireDAC.Comp.DataSet, FireDAC.Comp.Client, FMX.Dialogs, System.IOUtils;

//---------------------------------------------------------
// 테이블에서 조회한 데이터를 메인폼으로 넘겨줄 데이터 정의
type SAnsType = Record
   qCount : integer;
   sFd1, sFd2, sFd3, sFd4 : TStringList;
End;
//---------------------------------------------------------


type
  TDBsModule = class(TDataModule)
    FDConnection1: TFDConnection;
    FDQuery1: TFDQuery;
    procedure FDConnection1BeforeConnect(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    function Select_All: SAnsType;
  end;


var
  DBsModule: TDBsModule;

implementation

{%CLASSGROUP 'FMX.Controls.TControl'}

{$R *.dfm}

procedure TDBsModule.FDConnection1BeforeConnect(Sender: TObject);
begin
  {$IFDEF MSWINDOWS}
  FDConnection1.Params.Values[ 'Database' ] := ExtractFilePath(ParamStr(0)) + '..\..\stest.db';
  {$ENDIF}

  {$IF DEFINED(iOS) or DEFINED(ANDROID)}
  FDConnection1.Params.Values[ 'Database' ] :=                                //  StartUp\Library
           TPath.Combine( TPath.GetDocumentsPath(), 'stest.db');
  {$ENDIF}                                                                    // .\assets\internal
end;

function TDBsModule.Select_All() : SAnsType;
begin
  result.sFd1 := TStringList.Create;
  result.sFd2 := TStringList.Create;
  result.sFd3 := TStringList.Create;
  result.sFd4 := TStringList.Create;

  FDConnection1.Open;
  try
    FDQuery1.Close;
    FDQuery1.SQL.Clear;
    FDQuery1.SQL.Add( 'Select * from table1' );
    FDQuery1.Open;
    FDQuery1.First;

    // 조회한 데이터를 레코드에 저장.
    while Not FDQuery1.EOF do
    begin
      result.sFd1.Add( FDQuery1.FieldByName('field1').AsString );
      result.sFd2.Add( FDQuery1.FieldByName('field2').AsString );
      result.sFd3.Add( FDQuery1.FieldByName('field3').AsString );
      result.sFd4.Add( FDQuery1.FieldByName('field4').AsString );

      FDQuery1.Next;
      Inc( result.qCount );
    end;
    result.qCount := result.sFd1.Count;

  except
    on e: Exception do begin
      ShowMessage( e.Message );
    end;
  end;

  FDConnection1.Close;
end;

end.

 

샘플 프로젝트: 화면 Form Unit

화면 UI를 처리하는 메인폼 에서는 DBModule 에서 정의된 function 을 호출하여 조회한 데이터를 리턴값으로 받아와서 화면에 표현해 주면 된다.

unit MLocalDB;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Memo.Types, FMX.ScrollBox, FMX.Memo, FMX.Controls.Presentation,
  FMX.StdCtrls, FMX.ListView.Types, FMX.ListView.Appearances, FMX.ListView.Adapters.Base, FMX.ListView, FMX.Objects;

type
  TMForm = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    ListView1: TListView;
    Label1: TLabel;
    Rectangle1: TRectangle;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  MForm: TMForm;

implementation

Uses
  DBModule;       // Database 모듈 유닛 사용 선언

{$R *.fmx}

procedure TMForm.Button1Click(Sender: TObject);
var
  sdata : SAnsType;  // DBModule 유닛에 선언된 쿼리데이터 저장 레코드
  i: Integer;
  LItem: TListViewItem;
begin
  sdata :=  DBsModule.Select_All(); // 데이터 조회 하여 전송 받음

  ListView1.Items.Clear;
  ListView1.BeginUpdate();
  for i := 0 to sdata.qCount - 1 do
  begin
     LItem := ListView1.Items.Add;
     LItem.Text   := sdata.sFd1[ i ] + #32 + sdata.sFd2[ i ] + #32 + sdata.sFd3[ i ];;
     LItem.Detail := sdata.sFd4[ i ];
  end;
  ListView1.EndUpdate();

  Memo1.Lines.Add( sdata.qCount.ToString );
end;

end.

 

샘플 프로젝트: 소스 다운로드

 

이 댓글 링크
다른 사이트에 공유하기

  • 어드민 changed the title to 샘플을 통해 알아보는, 화면(UI) 요소와 비화면(비즈니스 로직, 데이터베이스 액세스 등) 요소 분리

이 토의에 참여하세요

지금 바로 의견을 남길 수 있습니다. 그리고 나서 가입해도 됩니다. 이미 회원이라면, 지금 로그인하고 본인 계정으로 의견을 남기세요.

Guest
이 토픽(기고/질문)에 답하기

×   서식있는 텍스트로 붙여넣기.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   이전에 작성한 콘텐츠가 복원되었습니다..   편집창 비우기

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

중요한 정보

이용약관 개인정보보호정책 이용규칙 이 사이트가 더 잘 작동하기 위해 방문자의 컴퓨터에 쿠키가 배치됩니다. 쿠키 설정 변경에서 원하는 설정을 할 수 있습니다. 변경하지 않으면 쿠키를 허용하는 것으로 이해합니다.