[Design pattern - 실무편] Database의 Transaction을 옵서버 패턴으로 구성하기


Study/Design Pattern  2019. 6. 18. 23:02

안녕하세요. 명월입니다.


이 글은 옵서버(Observer) 패턴을 실무에서 어떻게 사용되는 지에 대한 소개입니다.


사실 Observer 패턴은 Java와 C#에서는 람다식, javascript에서는 callback 식이기 때문에 매우 많이 사용됩니다.

특히, 완성된 기존에 소스에서 사양 변경을 크게 하지 않고 수정하고 싶을 때 사용되는 패턴이기도 합니다.


예를 들면 아래와 같은 소스가 있다고 가정합니다.

class Controller
{
  public void Run()
  {
    for(int i = 0; i < 100; i++)
    {
      Console.WriteLine(i);
    }
  }
}
class Program
{
  static void Main(string[] args)
  {
    var controller = new Controller();
    controller.Run();

    Console.WriteLine("Press any key...");
    Console.ReadKey();
  }
}

위 소스는 매우 간단하지만 Run의 소스를 최소화해서 짝수만 출력하고 싶다.

class Controller
{
  public void Run(Func<int, bool> func)
  {
    for (int i = 0; i < 100; i++)
    {
      if (func(i))
      {
        continue;
      }
      Console.WriteLine(i);
    }
  }
}
class Program
{
  static void Main(string[] args)
  {
    var controller = new Controller();
    controller.Run(i =>
    {
      return (i % 2) == 1;
    });

    Console.WriteLine("Press any key...");
    Console.ReadKey();
  }
}

아래와 같이 옵서버 패턴으로 부분만 수정할 수도 있습니다. 물론 위처럼 아주 간단한 소스에서 이렇게 바보같이 작성할 리는 없지만 매우 복잡한 소스에서는 의외로 유용할 경우가 있습니다.

그러나 옵서버 패턴을 너무 많이 사용하게 되면 소스가 매우 많이 복잡해지고 지저분해지는 것을 생각하고 작성해야 합니다.


사실 이 글에서는 옵서버 패턴의 활용에 대한 소개보다는 옵서버 패턴이 프로그램 디자인에서 어떨때 가장 많이 쓰일까에 대한 소개입니다.


데이터 베이스에서 우리가 Insert나 update, delete를 할 때, transaction을 걸어서 사용해야 합니다. 왜냐하면 중간에 Insert 처리나 update 중에 에러가 날 경우 롤백을 해야하기 때문이죠.

class DBExample
{
  private void Insert() { }
  private void Update() { }
  private void TransactionOpen() { }
  private void TransactionCommit() { }
  private void TransactionRollback() { }
  public void InsertDataProcedure()
  {
    try
    {
      // 트랜젝션 오픈
      TransactionOpen();
      
      // Insert 처리
      Insert();
      
      // Update 처리
      Update();

      // 오류가 없으면 Commit
      TransactionCommit();
    }
    catch
    {
      // 에러가 발생하면 Rollback
      TransactionRollback();
    }
  }
}

위의 형식처럼 작성하는 분이 많습니다. 솔직히 위 형식으로도 크게 소스가 지저분해 보인다거나 잘못된 처리를 하는 것은 아닙니다. 그러나 람다식을 이용하면 좀 더 transaction을 유용하게 사용할 수 있습니다.

class DBExample
{
  private void Insert() { }
  private void Update() { }
  private void TransactionOpen() { }
  private void TransactionCommit() { }
  private void TransactionRollback() { }

  // 결과 값이 List형식일때..
  protected IList<T> Transaction<T>(Func<IList<T>> func)
  {
    try
    {
      TransactionOpen();
      var ret = func();
      TransactionCommit();
      return ret;
    }
    catch (Exception e)
    {
      TransactionRollback();
      throw e;
    }
  }
  // 결과 값이 단일 클래스 형식일때..
  protected T Transaction<T>(Func<T> func)
  {
    try
    {
      TransactionOpen();
      var ret = func();
      TransactionCommit();
      return ret;
    }
    catch (Exception e)
    {
      TransactionRollback();
      throw e;
    }
  }
  // 결과 값을 예측할 수 없을 때
  protected dynamic Transaction(Func<dynamic> action)
  {
    try
    {
      TransactionOpen();
      var ret = action();
      TransactionCommit();
      return ret;
    }
    catch (Exception e)
    {
      TransactionRollback();
      throw e;
    }
  }
  // 결과 값이 Int형일 때
  protected int Transaction(Func<int> func)
  {
    try
    {
      TransactionOpen();
      var ret = func();
      TransactionCommit();
      return ret;
    }
    catch (Exception e)
    {
      TransactionRollback();
      throw e;
    }
  }
  // 결과 값이 없을 때
  protected void Transaction(Action action)
  {
    try
    {
      TransactionOpen();
      action();
      TransactionCommit();
    }
    catch (Exception e)
    {
      TransactionRollback();
      throw e;
    }
  }
  public void InsertDataProcedure()
  {
    String data = "Hello world";
    data = Transaction(() =>
    {
      Insert();
      //람다식은 영역 밖의 변수 참조가 가능하다.
      Console.WriteLine(data);
      Update();
      // String 식으로 리턴
      return data;
    });
  }
}

실제로는 위처럼 Transaction 함수를 만들어서 옵서버 패턴식으로 참조를 합니다. Transaction의 함수의 경우는 결과의 모든 경우의 수를 오버로딩을 선언했기 때문에 어떤한 형식으로도 리턴값을 받을 수 있습니다.


사실 옵서버 패턴을 잘 구성하면 다른 패턴보다 함수의 재사용성을 상당히 올릴 수 있습니다.


여기까지 Transaction을 옵서버 패턴으로 구성하는 방법에 대한 설명이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.