안녕하세요. 정말 간만에 글을 쓰네요. 뭐 구차한 핑계는 대지 않겠습니다. 그냥 제가 게을러서죠. 혹시나 제 글을 기다리신 분이 계셨다면 정말 죄송할 따름입니다.

이번 포스트는 제가 개발 팀장으로 있는 와 같은 개발 프레임워크에 대한 이야기 입니다. 이 글은 월간 마소에 기고될 내용입니다. 스압(스크롤 압박)이 있을 거라 예상됩니다. 중간에 짜르기가 뭐해서 그냥 하나의 포스트로 작성했습니다. 널리 이해해 주시기 바랍니다.

경고! 스압! (쉽게 말해서 내용이 조낸 길게 느껴질 수도 있슴돠)

프롤로그

우리가 어떤 소프트웨어를 개발할 때 말 그대로 아무것도 없는 것에서 무엇인가를 만들어내는 것은 불가능에 가깝다. 적어도 하드웨어를 제어하는 운영체제나 디바이스 드라이버와 같은 소프트웨어가 필요할 것이고 어플리케이션을 개발하는데 필요한 WIN32 와 같은 API 가 있어야 할 것이다. 사실 아무런 API도 제공되지 않는 운영체제를 개발하는 것이 아니라면, 다른 개발 환경의 도움 없이 무에서 유를 창조하는 소프트웨어 개발은 상상하기 어렵다.

그렇다면 운영체제와 운영체제가 제공하는 API 만을 가지고 소프트웨어를 개발하는 것은 어떠할까? 솔직히 말하면 운영체제가 제공하는 API 만으로 어플리케이션을 개발하는 것 또한 조낸 어렵다. 특히, 소위 말하는 기업용 어플리케이션을 개발하는 시스템 통합(SI) 프로젝트라면, 짧은 프로젝트기간에 아주 많은 프로그램을 작성해야 하기 때문에 WIN32 API 만으로 어플리케이션을 작성하기란 거의 불가능에 가깝다. 또 모르지, 돈으로 쳐 바른다면 개발이 가능할지 몰라도 유지/보수는 또 어떻게 해야 하남?

그렇다면 닷넷 프레임워크나 자바와 같은 보다 진보된 어플리케이션 환경은 어떨까? 이들은 운영체제 수준에서 제공하는 어플리케이션 개발 환경보다는 진보되어 있지만 이것만으로 높은 개발 생산성을 확보하면서 우수한 품질의 어플리케이션을 개발하기란 역시 쉽지 않다. 그래서 많은 개발 프로젝트에서 사용하는 것이 어플리케이션 개발 프레임워크(Application Development Framework)이다. 이번 포스트에서는 닷넷 환경에서 어플리케이션 개발 프레임워크란 무엇이며 어떤 장점을 가져다 주는지 노가리를 풀어보도록 하겠다.

이 글에서 몇몇 어플리케이션 개발 프레임워크의 예를 들겠지만, 구체적인 코드 예제는 필자가 직접 개발에 참여한 NeoDEEX를 사용할 것이다. 이 글의 목적은 어플리케이션 개발 프레임워크가 무엇인지, 그리고 어플리케이션 개발 프레임워크를 적용함으로써 개발 시 어떤 이익을 얻을 수 있는지 독자들에게 알리는 것이다. 이를 위해 어쩔 수 없이(응?) 구체적인 코드를 예로 들어야 하지만 필자가 모든 어플리케이션 개발 프레임워크에 대해 코드의 품질을 논의할 정도로 깊은 이해를 아직 갖추지 못했기 때문에 어쩔 수 없이(초큼 구차한 변명이다) 필자가 개발에 참여한 NeoDEEX를 예로 드는 것일 뿐이다. 다른 어플리케이션 개발 프레임워크 역시 비슷하거나 동등한 기능을 가지고 있을 것이므로 독자들이 사용하는 혹은 사용할 어플리케이션 개발 프레임워크에서 이러한 점들을 찾아보기를 권하는 바이다.

솔까말, 최근 를 내놓은 마당에 개발 프레임워크의 유용성을 알리면서 우리 프레임워크 홍보 좀 해보자고 쓰는 글이다. 미안타. 그래도 NeoDEEX 만 잘났어요 하는 글은 아니니 용서하길… 싫음 따른 사이트 가시면 되겠다.

어플리케이션 개발 프레임워크란?

어플리케이션 프레임워크라고도 불리는 어플리케이션 개발 프레임워크(이하 개발 프레임워크)는 어플리케이션을 개발하는 일련의 패턴을 말한다. 좁은 의미의 개발 프레임워크는 어플리케이션 개발에 사용되는 클래스 라이브러리, 공통 모듈 등을 말하며 넓은 의미의 개발 프레임워크는 클래스 라이브러리 외에도 명명 표준, 코딩 표준 등 코드 작성에 관련된 일련의 규칙뿐만 아니라 어플리케이션 아키텍처 표준까지 정의하고 있다.

닷넷 프레임워크도 개발 프레임워크 인가요?

닷넷 프레임워크를 개발 프레임워크라고 말하지 않는다. 닷넷 프레임워크는 이름에 “프레임워크”라는 단어가 포함되어 있을 뿐 어플리케이션 개발 프레임워크라고 보긴 그 범위가 너무 광범위하다. 닷넷 프레임워크는 일반적으로 어플리케이션 플랫폼(platform)으로 보는 것이 일반적이며 자바를 자바 플랫폼이라 부르지 개발 프레임워크라고 보지 않는 이유와 비슷하다.

개발 프레임워크들은 매우 다양하다. Win32 어플리케이션을 작성할 때 많이 사용되는 MFC(Microsoft Foundation Class) 라이브러리가 대표적인 “좁은 의미”의 개발 프레임워크이다. Win32 GUI 어플리케이션을 작성할 때 개발자들은 MFC를 통해 일련의 클래스들을 사용하여 일정한 패턴대로 개발을 하게 된다. 마이크로소프트의 Enterprise Library(EL) 역시 어플리케이션 개발 프레임워크이다. EL은 좁은 의미의 개발 프레임워크라고 보기엔 좀 애매한데, EL이 Pattern & Practice의 가이드를 따라서 작성된 것이기 때문이다. Pattern & Practice와 EL을 하나로 본다면 넓은 의미의 어플리케이션 개발 프레임워크라고 볼 수 있을 것이다. Spring.NET 역시 어플리케이션 개발 프레임워크이다. 필자가 아는 한 Spring.NET이 명명 표준이나 코딩 표준, 아키텍처 가이드 등을 제공하지 않기 때문에 좁은 의미의 개발 프레임워크라고 할 수 있겠다.

개발 프레임워크들은 대개 자바 환경에서 많이 개발되고 활용되어 왔다. 그 이유는 자바 어플리케이션들이 상대적으로 닷넷에 비해 개발 생산성이 떨어지기 때문에 개발 프레임워크를 통해 개발 생산성을 올리기 위한 것으로 보인다. 닷넷 환경에서는 통합된 개발 프레임워크가 사용되기보다는 데이터 액세스, 로깅(logging) 등등의 작은 클래스 라이브러리들이 지역적으로 사용되는 경우가 더 많은 것으로 보인다.

어플리케이션 개발 프레임워크의 장단점

그렇다면 왜 개발 프레임워크를 사용하여 어플리케이션을 개발하는 것일까? 개발 프레임워크를 사용하는 이유는 매우 다양하지만 가장 큰 이유로는 개발 생산성 증대, 어플리케이션 품질 향상을 들 수 있다. 이외에도 개발 프레임워크를 적용함으로써 얻을 수 있는 장점은 여러 가지가 존재한다. 개발 프레임워크의 장단점은 무엇인지 이제부터 구체적으로 살펴보도록 하자. 이번 칼럼의 주요 목적이 바로 개발 프레임워크를 사용함으로써 얻을 수 있는 장점을 알아보는 것이며 개발자들이 개발 프레임워크를 사용하여 보다 편리하게 어플리케이션을 개발하도록 유도하는 것이다.

개발 생산성 향상

대부분의 개발 프레임워크는 코드를 작성하는데 편리한 클래스 라이브러리를 제공한다. 개발자들은 이들 클래스 라이브러리를 통해 동일한 작업을 하는 코드를 보다 빠르고 간편하게 작성할 수 있다. 구체적인 예를 들어보도록 하자. [리스트1]은 ADO.NET 만을 사용하여 데이터를 조회하여 업데이트하는 트랜잭션 처리 코드이다.

   1: public sealed class AdoNetCode
   2: {
   3:     private string _connString;
   4:  
   5:     public AdoNetCode()
   6:     {
   7:         _connString = 
   8:             ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
   9:     }
  10:  
  11:     private string ConnectionString
  12:     {
  13:         get { return _connString; }
  14:     }
  15:  
  16:     public void UpdateProductSummary(int productId)
  17:     {
  18:         string query1 = 
  19:             "SELECT SUM(Quantity) FROM OrderDetails WHERE ProductId=@p0";
  20:         string query2 = 
  21:             "UPDATE ProductSummary SET SaleAmount=@p1 WHERE ProductId=@p0"; 
  22:         SqlConnection conn = new SqlConnection(this.ConnectionString);
  23:         SqlCommand cmd1 = new SqlCommand(query1, conn);
  24:         SqlCommand cmd2 = new SqlCommand(query2, conn);
  25:  
  26:         cmd1.Parameters.AddWithValue("@p0", productId);
  27:         TransactionOptions options = new TransactionOptions();
  28:         options.IsolationLevel =
  29:             System.Transactions.IsolationLevel.ReadCommitted;
  30:         options.Timeout = TimeSpan.FromSeconds(60);
  31:         using (TransactionScope scope = 
  32:                     new TransactionScope(TransactionScopeOption.Required, options))
  33:         {
  34:             try
  35:             {
  36:                 // 데이터 베이스 연결
  37:                 conn.Open();
  38:                 // 데이터 액세스 수행
  39:                 int amount = (int)cmd1.ExecuteScalar();
  40:                 // 쿼리 수행 결과를 다시 매개변수로 설정
  41:                 cmd2.Parameters.AddWithValue("@p0", productId);
  42:                 cmd2.Parameters.AddWithValue("@p1", amount);
  43:                 cmd2.ExecuteNonQuery();
  44:                 // 트랜잭션 커밋
  45:                 scope.Complete();
  46:             }
  47:             catch (SqlException ex)
  48:             {
  49:                 // 오류 로그를 남긴다.
  50:                 WriteErrorLog(ex);
  51:                 // 트랜잭션 롤백을 위해 예외를 전파해야 한다.
  52:                 throw;
  53:             }
  54:             finally
  55:             {
  56:                 // 데이터베이스 연결을 닫는다. (Open 오류 시 확인을 위해 Closed 상태 체크)
  57:                 if (conn.State != ConnectionState.Closed)
  58:                 {
  59:                     conn.Close();
  60:                 }
  61:             }
  62:         }
  63:     }
  64: }
[리스트1] ADO.NET 만을 사용한 트랜잭션 처리 코드 예제

[리스트 1]의 코드는 다소 장황해 보이지만 불필요한 코드는 거의 없다. SQL Injection 과 같은 보안 이슈를 막기 위해 매개변수화 된 쿼리(parameterized query)를 사용했으며, 성능을 고려하기 위해 2회의 데이터베이스 액세스를 하나의 데이터베이스 연결 내에서 처리 했고, 데이터베이스 연결이나 쿼리 수행 시 발생할 수 있는 오류를 처리하는 예외 처리 코드도 포함하고 있다. 다시 말해 [리스트 1]의 코드는 권장되는 데이터 액세스 코딩 패턴을 거의 모두 준수하였으며 성능과 오류 처리를 모두 포함하는 잘 작성된 코드라는 것이다.

보다 실제적인 예를 위해 예외 처리 코드와 트랜잭션 처리를 포함했지만 [리스트 1]의 코드 양은 결코 적지 않다. 특히, 데이터를 조회하고 업데이트 하는 핵심 코드 외에도 트랜잭션 관리, 예외 처리, 데이터베이스 연결 관리 등 코드의 안정성 및 관리성에 관련된 부가적인 코드의 양이 많음에 주목할 필요가 있다.

[리스트 2]의 코드는 개발 프레임워크(NeoDEEX)에서 제공하는 데이터 액세스 기능과 트랜잭션 등의 기능을 사용한 코드로써 [리스트1]과 동등한 작업을 수행한다. [리스트 2]의 Fox로 시작하는 클래스나 특성(attribute), 열거 타입에 대해서는 잊어 버리자. 이들 타입들은 특정 개발 프레임워크가 제공하는 것이므로 이들이 구체적으로 어떤 기능을 제공하는가는 여기서 중요하지 않다. 다만, 이 개발 프레임워크가 제공하는 기능으로 인해 보다 적은 양의 코드만을 작성해도 된다는 점을 주목하자. [리스트1]과 [리스트2] 두 코드의 성능 비교는 잠시 후에 다시 언급하도록 하겠다.

   1: [FoxTransactionController(FoxTransactionControllerKind.LocalTransaction)]
   2: class MyFrameworkCode : FoxDacBase
   3: {
   4:     [FoxTransaction(FoxTransactionOption.Required)]
   5:     [FoxAutoComplete]
   6:     public void UpdateProductSummary(int productId)
   7:     {
   8:         string query1 =
   9:             "SELECT SUM(Quantity) FROM OrderDetails WHERE ProductId=@p0";
  10:         string query2 =
  11:             "UPDATE ProductSummary SET SaleAmount=@p1 WHERE ProductId=@p0";
  12:         FoxSqlParameterCollection parameters1 = new FoxSqlParameterCollection();
  13:         FoxSqlParameterCollection parameters2 = new FoxSqlParameterCollection();
  14:  
  15:         parameters1.AddWithValue("@p0", productId);
  16:         parameters2.AddWithValue("@p0", productId);
  17:         // 데이터 액세스 수행
  18:         int amount = (int)this.DbAccess.ExecuteSqlScalar(query1, parameters1);
  19:         // 쿼리 수행 결과를 다시 매개변수로 설정
  20:         parameters2.AddWithValue("@p1", amount);
  21:         this.DbAccess.ExecuteSqlNonQuery(query2, parameters2);
  22:     }
  23:  
  24:     protected override void OnError(Exception ex)
  25:     {
  26:         IFoxLog log = FoxLogManager.GetLogger("ErrorLog");
  27:         log.Error(ex);
  28:     }
  29: }

[리스트2] 개발 프레임워크(NeoDEEX)를 사용한 트랜잭션 처리 코드

[리스트1]의 코드와 [리스트2]의 코드를 비교해 보면 개발 프레임워크를 사용한 코드의 양이 훨씬 적다는 것을 알 수 있을 것이다. 단순히 개발자가 작성해야 할 코드의 양이 적다는 것보다 중요한 것은 개발자가 트랜잭션을 관리하는 코드나 예외 로그를 기록하기 위한 코드, 데이터베이스 연결을 관리하는 코드를 작성하지 않거나 최소한의 코드만을 작성해도 된다는 점이 중요하다. 개발자가 반복적으로 작성해야 하는 지루한 코드들을 개발 프레임워크에서 제공해 줌으로써 개발자는 개발 생산성을 증대시킬 수 있으며 동일한 시간이 주어진다면 더 많은 양의 비즈니스 로직을 개발할 수 있게 된다.

[리스트 2]의 코드는 특정 개발 프레임워크가 제공하는 기능들을 사용한 것이지만 개발 프레임워크마다 개발 생산성 향상을 위해 제공하는 기능은 다양하다. MFC는 다양한 클래스 라이브러리가 주종이며 Spring.NET도 비슷하게 클래스 라이브러리를 제공하기도 한다. 몇몇 개발 프레임워크는 코드 생성 도구를 통해 반복적이고 지루한 코드를 자동으로 생성하는 경우도 있다(우리 NeoDEEX가 그렇다!). 만약 리스트 2의 코드에서 SQL 문장을 제외한 나머지 코드를 개발 프레임워크에서 생성해 주거나 제공해 준다면 어떠할까? 아마도 개발 생산성을 더욱 더 증대 시킬 수 있을 것이다(이젠 창피고 뭐고 없다! 우리 NeoDEEX가 바로 C# 코드를 생성해 준닷! -_-;).

어플리케이션 품질 향상

먼저 어플리케이션 품질이란 것이 무엇을 의미하는지 정의할 필요가 있겠지만 대개 높은 성능과 안정성, 정확한 계산 결과, 미려한 UI 등을 들 수 있겠다. 미려한 UI의 경우, 개발 프레임워크에 의해 향상된다기 보다는 디자인이 어떻게 적용되었는가에 영향을 받기 때문에 논외로 하겠다. 사실 UI를 미려하게 해주는 개발 프레임워크는 듣도보도 못했으므로 할 얘기가 읍다능……

개발 프레임워크가 어떻게 어플리케이션 성능에 영향을 미치는가를 이해하기 위해서 대개 어플리케이션의 성능이 저하되는 이유를 파악해 볼 필요가 있다. 낮은 하드웨어 스펙, 잘못된 설계, 어플리케이션과 맞지 않는 어플리케이션 아키텍처, 낮은 개발자 수준 등등 다양한 이유로 어플리케이션 성능이 저하될 수 있다. 예를 들어 [리스트 1]의 코드는 성능을 충분히 고려한 코드이다. 매개변수를 사용하는 쿼리(Parameterize Query), 2개의 데이터 액세스에 대해 하나의 데이터베이스 연결 사용, System.Transactions 를 사용함으로써 트랜잭션 오버헤드 감소, 적절한 트랜잭션 격리 수준(transaction isolation level) 사용 등등이 성능을 고려한 코드라고 볼 수 있는 것이다. 개발자들의 수준이 모두 훌륭하여 이와 같은 성능적인 고려 사항을 모두 감안하여 코드를 작성한다면 좋겠지만 실제 프로젝트에서는 이러한 성능관련 코드는 거의 고려대상이 아닌 경우가 허다하다. 필자가 아는 한 모든 개발 프로젝트는 시간에 쫓기기 마련이고 이렇게 부족한 개발 기간 동안 권장되는 코딩 패턴(보안, 성능)을 적용하기 힘들기 마련이다. 또 개발 프로젝트의 개발자들을 모두 고급 개발자로 채우기 또한 대단히 어렵다는 것은 독자들도 잘 알 것이다.

만약 [리스트 1]의 코드 대신 평범(?)하거나 초급 개발자가 작성한 [리스트 3]의 코드를 사용했다고 가정해 보자. [리스트 3]의 코드는 그럴 듯하다. 자주 사용하는 ExecuteScalar 호출과 ExecuteNonQuery 호출에서 반복적으로 등장하는 코드를 헬퍼 메서드로 분리하여 코딩 양을 줄였지만 [리스트 3]에서 별다른 문제점을 찾지 못한다면 벌써 문제는 발생한 것이나 다름 없다. [리스트 3]의 코드는 매개변수화 된 쿼리를 사용하지 않아 SQL Injection에 취약할 뿐만 아니라 예외 처리에 대한 코드가 전혀 없다. 이 뿐만이 아니다. 두 개의 데이터베이스 연결을 사용하여 데이터베이스 서버까지 2회의 라운트 트립을 수행하며 트랜잭션 프로모션(promotion)이 발생하여 성능이 저하된다. 물론, 예를 위해 고의 적으로 예제 코드를 짱구 같이 작성 했지만, 필자의 경험상 실제 프로젝트에서 [리스트 3]과 같은 코드를 찾기란 그다지 어렵지 않았다.

필자의 자체 부하 테스트(5 User, 로컬 데이터베이스 등등 테스트 환경이 궁금하면 별도 질문여~) 결과 리스트 1의 코드는 초당 859 트랜잭션(859 TPS)을 처리할 수 있었고 [리스트 3]의 코드는 603 TPS를 기록했었다. 다시 말해 고급 개발자가 작성한 코드와 초급 개발자가 작성한 코드가 성능적으로 30% 정도의 차이가 발생한다는 말이 된다. 필자의 경험상 [리스트 1]과 같은 코드를 작성하는 개발자보다 [리스트 3]과 같은 코드를 작성하는 개발자를 더 많이 보아왔기 때문에 전체적인 어플리케이션 코드 품질이 좋다고 말하기 어려운 것이다.

   1: public sealed class AdoNetCode
   2: {
   3:     ... 동일한 코드 생략 ...
   4:  
   5:     // ExecuteScalar 헬퍼 메서드
   6:     private object ExecuteScalarHelper(string query)
   7:     {
   8:         SqlConnection conn = new SqlConnection(this.ConnectionString);
   9:         SqlCommand cmd = new SqlCommand(query, conn);
  10:         conn.Open();
  11:         object result = cmd.ExecuteScalar();
  12:         conn.Close();
  13:         return result;
  14:     }
  15:  
  16:     // ExecuteNonQuery 헬퍼 메서드
  17:     private void ExecuteNonQueryHelper(string query)
  18:     {
  19:         SqlConnection conn = new SqlConnection(this.ConnectionString);
  20:         SqlCommand cmd = new SqlCommand(query, conn);
  21:         conn.Open();
  22:         cmd.ExecuteNonQuery();
  23:         conn.Close();
  24:     }
  25:  
  26:     public void UpdateProductSummaryBad(int productId)
  27:     {
  28:         string query1 = String.Format(
  29:             "SELECT SUM(Quantity) FROM OrderDetails WHERE ProductId={0}",
  30:             productId);
  31:  
  32:         using (TransactionScope scope = new TransactionScope())
  33:         {
  34:             // 데이터 조회
  35:             int amount = (int)ExecuteScalarHelper(query1);
  36:             // 수행 결과를 이용하여 데이터 업데이트
  37:             string query2 = String.Format(
  38:                 "UPDATE ProductSummary SET SaleAmount={0} WHERE ProductId={1}",
  39:                 amount, productId);
  40:             ExecuteNonQueryHelper(query2);
  41:             scope.Complete();
  42:         }
  43:     }
  44: }

[리스트3] 좋지 못한 ADO.NET 트랜잭션 처리 코드

개발 프레임워크는 일정한 코드 패턴을 제시함으로써 개발자의 수준을 평준화 할 수 있다. 또 그 코딩 패턴은 일반적으로 권장되는 좋은 코딩 패턴이기 때문에 성능에 좋지 못한 영향을 주는 경우는 거의 없다고 봐도 된다. 물론 개발 프레임워크 자체가 성능적으로 문제를 유발하는 구조를 갖추고 있을 수도 있지만 말이다.

개발 프레임워크를 사용한 [리스트 2]는 눈에 보이지 않지만 내부적으로 권장되는 코딩 패턴이 이미 적용되어 있으며 성능적인 고려가 적용된 코드가 사용되고 있다. 다시 말해 개발자의 수준에 상관없이 개발 프레임워크가 요구하는 코딩 패턴을 따르면 그 개발 프레임워크가 제공하는 수준의 성능을 낼 수 있다는 말이 된다. 이는 곧 개발 프레임워크가 개발자의 수준을 (상향 혹은 하향) 평준화시키게 되고 이로써 일정 수준의 성능이 나오게 된다는 말도 된다. [리스트 2]의 코드를 성능 테스트 해본 결과 797 TPS의 성능을 나타내었다. 고급 개발자가 많은 시간을 투자하여 작성한 [리스트 1]의 코드 보다는 약 7.2% 정도 느리지만 그렇게 큰 차이가 나지 않는다. 게다가 초급 개발자도 [리스트 2]와 같은 코드를 작성할 수 있다면 이야기는 전혀 달라지게 된다.

[그림 1]은 개발자 수준별 코드 품질과 평균적인 코드 품질을 보여주고 있다. 일반적으로 개발 프로젝트에서는 고급 개발자의 숫자가 절대적으로 부족하기 때문에 평균적인 코드 품질은 높지 않은 것이 사실이다. 하지만 개발 프레임워크가 제공하는 개발 패턴(클래스 라이브러리, 코딩 패턴)을 적용함으로써 초급 개발자도 일정 수준의 품질을 가진 코드를 작성할 수 있다는 점을 기억하자. 비록 개발 프레임워크가 고급 개발자의 코드를 하향 평준화 시킬 수 있지만 전체적으로 어플리케이션의 품질을 향상시키게 된다는 것이다.

image
[그림1] 개발 프레임워크의 코드 품질 평준화

개발 생산성의 연장선 상에서도 개발 프레임워크는 어플리케이션의 품질을 향상할 수 있다. 다시 [리스트 1]과 [리스트 2]를 비교해 보면 단순히 코드가 짧다는 것 외에도 개발 프레임워크의 장점을 보여준다. [리스트 1]의 코드는 이 코드의 핵심이라 볼 수 있는 데이터 액세스(SQL 문장) 외에도 데이터베이스 연결, 트랜잭션 처리, 예외 처리, 로깅 등 다양한 코드가 많이 존재한다. 반면 [리스트 2]의 코드는 핵심적인 어플리케이션 로직(SQL 문장) 이외의 기능은 프레임워크에서 처리해 주는 것을 알 수 있을 것이다. 개발자는 개발 프레임워크를 사용함으로써 어플리케이션 로직([리스트 2]의 경우에는 SQL 문장)에 보다 집중할 수 있으며 보다 정확하고 빠른 SQL 문장을 작성할 여유를 가질 수 있으며 이는 곧 어플리케이션 품질 향상에 직결된다는 것이다.

실례로, 개발 프레임워크를 적극 도입한 P 사의 개발 프로젝트에서는 초기 계획과 달리 개발 기간을 1개월 줄이고 테스트 기간을 1개월 늘렸다. 개발 기간이 1개월 단축되었음에도 불구하고 개발자들은 주어진 기간 내에 단위 테스트 수준을 완료한 프로그램을 모두 작성할 수 있었고 그 결과 시스템 연계 테스트, 성능 테스트 등을 더 오래 할 수 있었으며 아무런 문제 없이 시스템을 오픈 할 수 있었다. 개발 프레임워크의 개발 생산성이 직접적으로 프로젝트 기간을 단축시키지 않더라도 어플리케이션의 품질을 향상시킬 수 있다는 것을 보여준 실질 프로젝트라고 할 수 있을 것이다.

기타 개발 프레임워크의 장점들

기업용 어플리케이션은 대개 수 년 동안 변화하는 비즈니스 환경을 반영하기 위해 지속적으로 업데이트되기 마련이다. 다시 말해 개발이 완료된 기업용 어플리케이션이라 할지라도 지속적으로 기능이 추가/변경된다 것이다. 이런 이유에서 유지보수 기간에 담당자의 변경이나 퇴사는 어플리케이션 유지보수의 어려운 점으로 작용하기 마련이다. 개발 프레임워크를 적용함으로써 이러한 문제를 어느 정도 완화할 수 있다. 개발 프레임워크가 잘 적용된 어플리케이션은 서로 다른 개발자가 작성한 코드일지라도 그 패턴이 매우 유사하기 때문에 담당자의 변경이 발생하더라도 개발자가 빠르게 적응이 가능하다.

또한 대부분의 개발 프레임워크들은 유지/보수의 편의를 고려하고 설계 개발되기 때문에 프로그램 코드의 변경을 최소화하면서 어플리케이션의 기능을 수정할 수 있는 경우가 많다. NeoDEEX를 포함하여 많은 개발 프레임워크들은 어플리케이션의 SQL 문장을 별도의 XML 파일에 기록해 두고 이 쿼리를 런타임에 읽어 수행하는 SQL 매퍼(DB 매퍼라고도 한다) 기능을 제공한다. 만약 쿼리가 변경되면 SQL 문장이 기록된 XML 파일만을 수정하여 배포하면 된다(물론 컬럼 추가 등과 같은 UI 변경을 유발하지 않는 상황일 것이다). SQL 매퍼의 엔진은 XML 파일의 변경을 탐지하고 이 파일을 다시 읽어 들이기 때문에 어플리케이션 중단 없이 변경된 쿼리를 적용할 수 있다. 이와 같은 기능은 어플리케이션 코드를 변경하고 재 컴파일 하는 과정과 변경된 바이너리를 다시 배포하는 과정을 요구하지 않기 때문에 유지 보수를 보다 편리하게 해 주곤 한다. 물론 쿼리가 외부 XML 파일에 존재하기 때문에 성능상 불이익은 감수해야 한다.

이와 같이 개발 프레임워크는 어플리케이션을 유지/보수하는 데에도 일정한 기여를 한다고 볼 수 있다. 이외에도 개발 프레임워크로 얻을 수 있는 장점은 매우 다양하지만 개발 프레임워크 마다 특징적인 장점들도 있기 때문에 이 칼럼에서는 이 정도만을 다루기로 하겠다.

개발 프레임워크의 단점

개발 프레임워크가 갖는 단점은 개발자들이 이 개발 프레임워크에 익숙해 지는데 시간이 필요하다는 것이다. EL, Spring.NET, NeoDEEX 등의 상당한 수준의 개발 프레임워크들은 그 기능들이 매우 다양하기 때문에 개발자들이 이들 프레임워크에 익숙해지는데 일정 시간을 투자해야만 한다. 다만, 개발 프레임워크의 기능, 문서화 수준, 교육 지원 여부에 따라서 이 시간은 각각 달라질 수 있다. 일반적으로 기술 지원을 받을 수 있는 개발 프레임워크 제품은 개발자들이 개발 프레임워크에 익숙해지는데 적은 시간을 요구한다.

개발 프레임워크가 내포할 수 있는 또 다른 단점은 개발 프레임워크 자체가 낮은 수준이거나 버그를 가지고 있는 경우 이다. 예를 들어 개발 프레임워크가 성능을 장담할 수 없는 어플리케이션 아키텍처를 강요하거나 복잡한 구조로 인해 개발 생산성을 오히려 떨어트릴 수도 있다. 따라서 개발 프레임워크를 적용하고자 한다면 개발하고자 하는 어플리케이션에 가장 알맞은 개발 프레임워크를 선정하는 과정을 따라야 한다. 이 과정을 무시했다가는 전체 어플리케이션 코드에 지대한 영향을 주는 개발 프레임워크 때문에 개발/운영/유지/보수에 어려움을 겪을 수도 있다는 것을 잘 인식해야만 할 것이다. 개발 프레임워크 선정에 관련해서는 잠시 후에 다시 언급하도록 하겠다.

또 다른 개발 프레임워크의 단점은 유연성 부족이다. 개발 프레임워크가 아무리 잘 설계되고 다양한 상황을 고려하여 코딩 패턴을 제시한다 할지라도 모든 상황을 다 커버할 수 없다. 따라서 개발 프레임워크가 지원하지 않는 시나리오의 코드를 작성해야 한다면 상당히 어려움이 따를 수도 있다. 개발 프레임워크에 따라 유연성의 정도가 다를 수 있으므로 어떤 프레임워크는 지원하지 않는 어플리케이션 아키텍처, 클라이언트/서버 통신 방법 등 시나리오를 전혀 지원하지 않을 거나 지원하기 위해 아주 많은 코드를 작성해야 할 수도 있으며 어떤 프레임워크는 다양한 커스터마이징 방법을 제공할 수도 있다. 대개의 경우 개발 프레임워크가 지원하지 않는 시나리오에서 개발 프레임워크를 적용하기는 쉽지 않다. 이 때문에 개발 프레임워크를 얼마나 손쉽게 커스터마이징 할 수 있는가가 개발 프레임워크를 우수성을 비교하는 잣대가 되기도 한다.

마지막으로 언급할 부분은 개발 프레임워크를 도입하는데 비용이 발생할 수 있다는 점이다. 오픈 소스로 제공되는 EL, Spring.NET 은 초기 도입 비용이 발생하지 않을 수도 있다. 하지만 기업의 표준 개발 프레임워크로써 이들 오픈 소스 프레임워크는 또 다른 비용을 유발할 수도 있다. 이들 오픈 소스 프레임워크는 일반적으로 일관된 조직의 조직적인 기술 지원을 받을 수 없는 경우가 많다. 따라서 주어진 개발 프레임워크 소스를 수정하여 커스터마이징 된 개발 프레임워크를 사용하다가 발생되는 문제나 버그는 스스로 해결해야 하며 이 문제 해결이 매우 어려울 수도 있다. 또한 새로운 버전의 개발 프레임워크를 도입하고자 할 때는 이전 버전에서 수정했던 커스터마이징을 새로운 버전에 대해 동일하게 반복해야 할 수도 있다. 반면 유료 개발 프레임워크는 초기 도입 비용이 필요하지만 조직에 의해 교육 지원, 기술 지원을 받을 수 있으며 판매 회사에서 업그레이드를 수행하므로 장기적인 관점에서 소요되는 비용을 줄일 수도 있다. 이러한 사항들 역시 개발 프레임워크 선정에서 고려해야 할 사항이다. (NeoDEEX는 무료 버전과 유료 버전을 가지고 있슴돠. 물론 소스 공개는 안함돠.)

개발 프레임워크 적용 시 고려 사항

개발 프레임워크를 적용했을 때의 장/단점을 충분히 이해했고 개발 프레임워크를 도입하기로 마음 먹었다면 이제 어떤 개발 프레임워크를 도입할 것인가에 대해서 검토를 해야만 할 것이다. 개발 프레임워크를 도입하고자 했을 때의 고려 사항들을 살펴보도록 하자.

개발 프레임워크 선정

개발 프레임워크의 필요성을 느꼈다면 어떤 개발 프레임워크를 선정할 것인가를 고민해야 할 때이다. 닷넷 환경에서 사용할 수 있는 개발 프레임워크는 다양하다. EL은 마이크로소프트가 만들어서 배포하는 오픈 소스 개발 프레임워크이며 Spring.NET 혹은 iBatis.NET 등은 자바 진영에서 사용하던 개발 프레임워크를 닷넷화 한 것이다. 특히, Spring.NET은 거의 닷넷을 위해 새로이 작성된 것이라고 보아도 무방할 정도이며 단순한 포팅 수준이 아니라는 것을 알 수 있을 것이다. 오픈 소스 외에 상용으로 판매되는 개발 프레임워크는 별로 많지 않다.

개발 프레임워크를 선정하기 위해서 가장 먼저 고려해야 할 것은 개발 프로젝트에서 개발하고자 하는 어플리케이션에서 해당 개발 프레임워크를 적용 가능한가를 생각해야 한다. 개발 프로젝트의 목표가 데스크톱에서 작동하는 유틸리티 어플리케이션을 작성하는 것이라면 앞서 언급한 개발 프레임워크는 그다지 유용하지 않다. 이들은 모두 기업용 LOB(Line Of Business) 어플리케이션이나 ERP 패키지 등을 위한 개발 프레임워크에 가깝지 데스크톱 어플리케이션을 위한 개발 프레임워크가 아니기 때문이다. 특히, EL 이나 Spring.NET 등 대다수의 개발 프레임워크는 UI를 위한 기능을 포함하지 않는다는 점도 고려해야 한다. 다시 말해 개발하고자 하는 어플리케이션에 알맞은 개발 프레임워크들이 어떤 것들인지 후보 목록을 파악해야 한다는 것이다.

다음 고려해야 할 사항은 도입 비용이다. 오픈 소스 기반의 개발 프레임워크는 초기 도입 비용이 없다. 하지만 앞서 간단히 언급한 대로 도입한 개발 프레임워크를 그대로 사용할 것인지 아니면 일정 수준의 커스터마이징을 한 후에 사용할 것인지에 따라서 추후에 추가 비용이 발생할 수도 있다. 또 한가지 간과해서는 안될 것은 개발 프레임워크에 대한 교육이나 기술 지원과 같은 부수적인 지원을 받을 수 있는가도 고민해야 할 것이다. 초기 도입 비용이 없어서 오픈 소스 개발 프레임워크를 선정했지만 개발이 진행되면서 발생되는 기술 문제를 해결하는데 어려움을 겪을 수도 있기 때문이다. 제품으로 판매되는 개발 프레임워크라면 초기 도입 비용이 들겠지만 제조사로부터 지속적으로 교육 지원, 기술 지원, 트러블 슈팅 지원 등을 받을 수 있기 때문에 전체 비용이 감소할 수도 있다. (아무리 그래도 예산 없으면 무료 버전이나 오픈 소스를 쓸 수 밖에……)

대개 이 정도의 비교를 했다면 오픈 소스 중 1-2개, 유료 제품 중 1-2개 정도가 최후의 대상으로 남게 된다. 이제 최종 결정을 위해서 각 개발 프레임워크의 기능이나 성능을 구체적으로 비교하는 벤치마크 테스트(BMT)를 수행해 볼 수 있다. 도입하는데 충분한 시간을 가지고 있다면 개발하고자 하는 어플리케이션에서 가장 많이 발생하는 시나리오를 정의하고 이들 시나리오를 각각의 개발 프레임워크를 적용하여 개발을 해 보고 가능하다면 성능 테스트도 수행해 보는 것이 좋다(상당한 시간이 소요될 수도 있다). 이렇게 BMT 완료 후 최종적으로 하나의 개발 프레임워크를 선정하면 되는 것이다.

개발 프레임워크 적용

개발 프레임워크를 선정하여 사용하기만 하면 저절로 개발 생산성이 향상되고 어플리케이션의 품질이 향상되는 것은 아니다. 개발 프레임워크를 적용하면 개발 생산성이 향상되고 코드 품질이 향상될 개연성을 높여주는 것이지 무조건 개발 기간을 단축하고 우수한 응답 속도를 가진 어플리케이션을 보장하는 것이 아니기 때문이다. 그렇다면 어떻게 개발 프레임워크를 적용하는 것이 좋을까?

첫째는 개발 팀에서 개발 프레임워크를 적용하고자 하는 강력한 의지가 필요하다. 왜 이런 이야기를 하는가 하면, 개발 프레임워크에 대한 개발자들의 반발심 때문이다. 일반적으로 개발자들은 자신의 코드에 대한 자존심이 매우 강하다. “내 코드가 저 따위 라이브러리 코드보다 훨씬 더 좋아” 혹은 “저 라이브러리를 가져다 쓰느니 내가 첨부터 짜고 만다”라는 생각을 한 두 번쯤 안 해본 개발자는 아마 없으리라 생각한다(필자도 마찬가지다!). 필자가 참여한 한 프로젝트에서 프리랜서 개발자가 작성한 코드에 버그가 있었다. 개발 팀에서는 해당 코드의 버그를 지적하고 수정을 요구했었다. 그럼에도 불구하고 그 개발자는 자신의 코드에는 문제가 없으며 이전에도 여러 번 그런 식으로 코드를 작성했었다고 고집했다. 결국 버그를 재현해서 보여주자 그 개발자는 “다른 프로젝트에서도 이렇게 코딩 해서 문제가 없었다”라며 고개를 갸웃거리며 코드를 수정했었다.

이처럼 개발자는 자신의 코드에 대한 자부심이 대단히 강하며 특히 고급 개발자일수록 이러한 성향은 더욱 강하다. 이런 상황에서 개발 프레임워크의 코딩 패턴을 개발자들에게 강요하면 반발이 생길 수 있으며 어찌 보면 당연하다고도 할 수 있겠다. [그림 1]에서 보듯이 개발 프레임워크를 적용하면 상향 평준화와 하양 평준화가 동시에 발생하므로 고급 개발자들에게 개발 프레임워크를 적용함으로써 얻을 수 있는 균등한 코드 품질, 담당자 교체의 적은 부담감 등을 설득하여야 할 것이다.

두 번째로 필요한 것은 개발 프레임워크에 기초한 개발 표준을 확립하고 개발자들에게 주지시키는 것이다. 개발 프레임워크가 도깨비 방망이가 아니라는 점을 항상 잊지 말아야 한다. 개발 프레임워크를 사용하더라도 리스트 3과 같이 좋지 못한 코드가 나올 수도 있다. 단순히 개발 프레임워크를 도입하는 것만으로 개발 생산성이 향상되고 코드 품질이 좋아질 수 없다. 개발 프레임워크에서 제시하는 코딩 패턴을 개발 표준으로 지정하고 문서화 하여 개발자들이 숙지할 수 있도록 해야만 한다. 그리고 구축된 개발 표준을 개발자들이 따를 수 있도록 일정 부분 강제해야만 한다. 앞서 예를 들었던 P 사의 개발 프로젝트에서는 개발 프레임워크에 기반한 개발 표준을 구성하였고 개발자들의 코드가 개발 표준을 준수하는지 꾸준히 코드 리뷰를 수행하였으며 이 코드 리뷰 덕에 개발 프레임워크가 프로젝트의 개발 표준으로써 자리를 잡을 수 있었다.

당부의 말

사실 쩌는 고급 개발자들로만 개발 팀이 구성되어 있고 넉넉한 예산에 충분한 프로젝트 기간을 가진 프로젝트라면 개발 프레임워크 없이도 개발 생산성과 코드 품질, 어플리케이션 성능 등등에 문제가 없을 것이다. 하지만 실제 프로젝트에서는 부족한 예산과 부족한 프로젝트 기간, 개발자 부족에 허덕이기 마련이다. 이러한 열악한 프로젝트 환경을 조금이나마 개선시킬 수 있는 것이 바로 개발 프레임워크가 될 수도 있다. 개발 프레임워크를 도입하고 적용함으로써 부족한 시간과 경험 많은 개발자 부족을 어느 정도 메꿀 수 있다는 것을 알리는 것이 이 글의 목적이다.

이번 칼럼은 오래 전부터 쓰고 싶었던 “개발 프레임워크”에 대한 글이다. 사실, 필자가 상용으로 판매되는 개발 프레임워크 개발 팀의 팀장이기 때문에 이런 토픽에 대한 글을 쓰기 부담스러웠던 것은 사실이다. 아무래도 팔은 안으로 굽지 않겠는가? 그럼에도 불구하고 용기를 낸 이유는 이 글이 특정 제품의 홍보처럼 보이더라도(맞다. 홍보다… 비겁한 변명은 하지 않겠음) 독자 여러분이 개발 프레임워크를 통해 개발 환경을 향상시킬 수 있기를 바랬던 것이다. 오픈 소스이건 필자의 회사 제품이건 혹은 경쟁사의 제품이건 상관없이 적절한 개발 프레임워크 선정 과정과 적용 의지만 있다면 개발 프레임워크는 분명 좋은 방향으로 작용할 것임은 분명하다는 것을 말하고 싶다(요건 정말 진심 입니당~~).


경고 : 이 글을 무단으로 복제/스크랩하여 타 게시판, 블로그에 게시하는 것은 허용하지 않습니다.