본문 바로가기
Software Architect/SI 프로젝트 가이드

개발 프레임워크 설계

by romainefabula 2022. 3. 21.

소프트웨어 아키텍처에 대한 설계가 끝나면 WAS에서 수행될 업무프로그램 개발의 기본이 되는 개발프레임워크를 구성해야 한다. 일반 웹이라면 Spring과 MyBatis를 사용하면 되겠고, 배치라면 Spring Batch 등을 이용해서 구성하게 된다. 중요한 것은 소프트웨어 아키텍처 요구사항에 만족하도록 해야 하는 것이다. 그러므로, 웹이라고 무조건 Spring MVC 기반으로 하는 것이 아니고 요구사항에 부합하는 최적의 조합을 찾아야 하는 것이다.

일단 요구사항이 나오면 그에 적합한 여러가지 솔루션이나 라이브러리를 조사한다. 그중 기능, 성능, 가격, 개발편의성 등에 따라 한 가지를 선정하는 과정을 거친다. 별도 프로젝트로 실제로 프로그램을 만들어서 BMT를 수행하면서 비교해 볼 수도 있지만, 실제 개발프로젝트를 이미 시작한 경우는 그렇게 시간이 많지 않아서 프레임워크 비교 사이트에 올라온 정보 등을 이용해 하나를 선정하거나 두 개 정도를 선정한 후 실제로 개발해서 비교할 수도 있다.

 

개발 프레임워크 구성

개발프레임워크를 Spring으로 구성하게 되면 Spring MVC, Spring, MyBatis 정도의 조합이 될 것이다. 그런데, 여기에는 추가적으로 버전 선택의 문제가 남아 있다. 현재 Spring 다운로드 사이트에 가 보면 3.2, 4.3. 5.0 등 다양한 버전이 존재한다. 일단 이 중에서 하나를 선택해야 한다. 일단 선택의 첫번째 기준은 필요한 기능이 모두 제공되는 버전인가 확인하는 것이다. 그 다음은 안정된 버전인가이다.

프레임워크 버전 선정

국제 표준으로 버전 번호를 붙이는 데 대한 정의가 나와 있진 않다. 사실 버전은 만드는 사람이 마음대로 붙이게 되어 있다. 다만, 거의 표준처럼 사용하는 기준은 {major_version}.{minor_version}.{bug_patch} 또는 {major_version}.{minor_version}_{bug_patch} 형태이다. Major version은 소프트웨어의 구조나 기능적으로 대대적인 변화가 있을 때 1이나 그 이상을 올린다. Minor version은 기능적으로 일부 기능에 대한 변경이 발생했을 때 올린다. 마지막으로 bug patch는 이전 버전에서 발견된 버그(기능적 오류)에 대해 수정된 버전을 의미한다. 그리고, 추가적으로 버전 뒤에 붙은 것이 GA, Stable, SNAPSHOT, Nightly Build 등이 있다. GA나 Stable은 개발팀이나 업체에서 안정적으로 구현되었으니 믿고 사용해도 된다는 의미이고, SNAPHOT이나 Nightly Build는 수시로 계속 바뀌고 언제든지 개발팀에서 클래스나 메소드명까지 바꿀 수 있는 버전이다. 나도 예전에 SNAPSHOT 버전의 라이브러리를 참조해서 사용하다가 라이브러리 개발자가 갑자기 참조하는 라이브러리와 코드를 바꿔 버리는 바람에 대대적으로 코드를 수정한 적이 있다. 그러므로, SNAPHOT이나 Nightly Build는 되도록 사용하지 않는 것이 좋다.

위 버전에 대한 설명을 보면 대강 어떤 버전을 선택할지 감이 올 것이다. 현재 Spring에서 다운로드 받을 수 있는 버전은 5.0.4 SNAPSHOT, 5.0.3 GA, 4.3.15 SNAPSHOT, 4.3.14 GA, 3.2.18 GA가 있다. 일단 SNAPSHOT 버전을 제외하면 GA 버전으로 5.0.3, 4.3.14, 3.2.18이 남는다. 여기서, 세 버전 모두 필요한 기능을 모두 만족한다고 하면 일단 높은 버전의 5.0.3이나 4.3.14가 남게 된다. 여기서부터는 Bug patch 버전을 기준으로 선정한다. 어떤 프로그램이나 많이 수정하거나 새로 만든다면 보이지 않는 많은 버그가 존재할 수 있다. 이런 것이 발견될 때마다 Bug patch 버전이 올라간다. 그러므로, Bug patch 버전이 올라갈수록 보다 안정적일 가능성이 높다. (물론, bug patch를 하다가 새로운 bug가 생길 수가 있긴 하지만, 대체적으로 그렇다는 얘기다.) Bug patch 버전을 보면 5.0은 3 밖에 안 되기 때문에 불안정할 가능성이 높고 4.3.14가 안정적일 것 같다.

이렇게 해서 나라면 4.3.14를 사용할 것 같다. 하지만, 여기서도 SWA 개인의 취향이 존재한다. 어떤 사람들은 보다 최신버전을 설치해서 최신 기능을 누리는 것을 좋아하는 사람이 있고, 나처럼 보수적인 기준을 갖고 최대한 안정적으로 구성하는 것을 좋아하는 사람도 있다. 최신버전을 좋아한다면 수시로 최신 bug patch 버전을 검색해서 적용하면서 프로젝트에 적용해야 하고, 보수적인 기준을 가진 사람은 문제가 생기기 전까진 버전에 관해 의심할 일이 거의 없다.

이렇게 Spring의 버전을 선택했다면 다음으로 여기에 연관되는 라이브러리와 버전을 선택해야 한다. 보통 Spring의 pom 등에 참조하는 라이브러리에 대한 정보가 있으니, Maven 등을 적용하면 알아서 Spring 개발시 사용된 라이브러리와 참조 버전까지 알아서 가져 오므로 고민할 필요가 거의 없다. (Maven은 이런 점은 참 편리한데 꼬이기 시작하면 정말 피곤하게 만드는 일이 많다.) 가끔씩 A와 B라는 라이브러리를 넣는데 여기서 공통적으로 C라는 라이브러리를 참조하는 경우가 있다. 그런데, 하필 같은 C라는 라이브러리 중에 A가 참조하는 버전과 B가 참조하는 버전이 다른 경우는 문제의 소지가 있다. 이런 경우 Maven은 자동으로 높은 버전을 참조하는 것 같긴 한데, 두 버전이 버그패치 정도면 문제가 없겠지만 높은 버전이 되면서 일부 클래스나 메소드가 빠지거나 이름이 바뀌면 낮은 버전을 참조하던 곳에서 클래스나 메소드가 없다는 에러가 발생할 수 있다. 그렇다고 두 버전 다 넣으면 같은 이름의 클래스 중에 어떤 게 사용될지 예측하기도 어렵다. 이 문제에 대해선 명쾌한 해결책을 주기가 참 어렵다. 그냥 높은 버전만 넣고 돌려서 문제 없으면 다행이고, 클래스나 메소드가 없다고 하면 두 버전 다 넣고 해서 문제가 없어지길 바라는 방법 뿐이다. 이래도 안 되면 A, B 라이브러리의 버전을 조정해서 C의 같은 버전을 참조하도록 한다거나... (이래서 SWA가 피곤하다. 흰머리가 안 날 수가 없다.) 가장 확실한 방법은 구성해 놓고 필요한 모든 기능을 테스트해서 문제가 발생하지 않는 라이브러리 세트를 구성하는 것이다.

 

부가기능 구성

이렇게 프레임워크의 주가 되는 Spring(또는 다른 무언가) 버전까지 결정이 되었으니, 주변 기능에 대한 설계를 해야 한다.

Front-end

화면이 그냥 JSP면 보통 방식으로 하지만 그래도 화면에서 AJAX 방식을 구현한다면 Controller에선 JSP로 넘기는 방식과 JSON 송수신 기능도 필요하다. 화면 솔루션을 사용한다면 그 송수신 방식에 맞게 요청을 해석하고 응답을 구성하는 기능도 준비해야 한다. REST 방식으로 통신하는 화면 솔루션이라면 Controller도 RESTful로 설계한다. 송수신 메시지 형식에 따라 Message Converter도 필요하다.

Session

사용자 인증이 필요하다면 토큰이나 세션정보를 이용하는 인증모듈도 추가해야 한다. 그리고, 인증을 포함해서 서버 프로그램 수행시 오류가 발생했을 때 화면에서 인식할 수 있는 오류정보를 생성해서 화면으로 전달되게 한다. 클라이언트에서 예상하는 방식으로 정확한 형식의 응답을 보내도록 해야 한다는 것이다. 클라이언트가 HTML을 예상한다면 HTML 코드를, JSON을 예상한다면 JSON 포맷의 응답을 보내야 한다. WAS에서 세션방식으로 인증처리를 한다면 세션에 담는 정보를 최소화해야 하고, 세션타임아웃도 최소한으로 설정하도록 해야 한다. 한 사용자의 세션에 저장된 정보에 현재 접속 사용자수를 곱한만큼 힙메모리를 차지하기 때문에, 세션에 저장하는 정보가 많은데 접속사용자가 몰리면 메모리를 많이 사용하게 된다. 그리고, 세션 타임아웃이 길면 접속해서 잠깐 사용하고 로그아웃을 하지 않은 경우, 사용자가 사용하지 않는데도 세션 타임아웃에 도달할 때까지 세션정보는 계속 힙메모리를 차지하고 있게 되므로 결국 이런 불필요하게 유지되는 세션정보들에 의해서도 OutOfMemory가 발생할 수 있는 것이다.

Message

프로그램 처리 결과를 사용자에게 전달하도록 메시지 처리 기능도 준비한다. 한 개 언어에 대한 처리만 필요하다면 단순히 코드와 그에 대한 메시지만 정의하면 된다. 하지만, 사용자가 다양해 여러 언어를 지원해야 하는 경우라면 다국어 처리도 추가해야 한다. 메시지 처리는 어떤 시스템이나 하는 것이지만 다국어 처리는 진짜 필요한지 요구사항 조사 때 반드시 확인해야 한다. 화면에 있는 내용에 대해 다국어 처리를 하려면 최초 개발시에도 하나씩 코드처리를 해야 하지만, 나중에 유지보수할 때에도 내용이 모두 화면소스에 실제 내용 대신 메시지코드만 보이기 때문에 한눈에 내용이 들어오지 않아 수정할 때마다 코드와 내용을 확인해야 하므로 그만큼 시간이 걸리기 때문이다. 다시 말해, 다국어 처리는 꼭 필요하지 않으면 안 하는 게 좋다.

Persistence

DB를 이용해 데이터를 저장하고 조회해야 한다면 MyBatis, Hibernate 등의 Persistence Layer 처리 라이브러리를 선택하고, 데이터소스 구성과 트랜잭션 처리에 대한 설계도 필요하다. 하나의 DB만 사용하면 그나마 간단하지만, 2개 이상의 DB에 대해 트랜잭션 처리가 필요하면 2PC(Two Phase Commit) 처리까지 해야 되므로 구성이 훨씬 어려워진다. 2PC는 속도에도 손해가 있고 오류가 발생했을 경우 해결하기가 아주 어려우니까 되도록 사용하지 않고 구현하는 방법을 찾는 것이 좋다.

Log

로그에 대한 설계도 필요하다. 로그 포맷을 어떻게 하고, 종류에 따라 로그파일을 어떻게 분리하고 어떤 주기로 rotate할지도 결정해야 한다. 그리고, DB 쿼리 수행에 대해서는 쿼리만 기록할지, 쿼리 결과까지 기록할지도 정해야 한다.
로그는 보통 서버 단계(로컬, 개발, 운영 등)에 따라서 레벨을 다르게 적용한다. 운영 전까지는 Debug 정도의 레벨로 기록해서 오류가 발생했을 때 되도록 많은 정보를 이용해서 문제를 빨리 찾도록 하고, 운영부터는 Info 정도의 레벨로 흐름을 파악하기 위한 기본정보와 오류에 대한 상세한 정보를 기록하도록 한다. 운영에서 Warn이나 Error가 아닌 Info를 추천하는 이유는 Info 로그가 없으면 오류가 발생하기 전에 이 기능을 실행하기 위해 필요한 모듈이 로딩되었는지조차 찾기가 어려워서다. Info 로그에는 흐름을 파악하기 위한 최소한의 정보를 기록하는 습관을 들이면 Info 로그 때문에 과도한 로그가 생기진 않는다.
로그에 관해서 가장 중요한 것은, 모든 오류에 대해서 반드시 추적이 가능한 수준의 상세한 기록이 남도록 해야 한다는 것이다. 예를 들어, catch 절에서 SQLException을 잡아서 이것에 대한 기록을 하지 않고 새로운 Exception 객체에 조회실패라는 메시지만을 담아서 리턴하면, 이것이 DB접속실패인지, 커넥션풀에 사용할 수 있는 커넥션이 없는 것인지, SQL문의 오류인지 알 수가 없다. 그러므로, 최초에 catch한 SQLException을 Error 레벨로 기록하거나, 새로운 Exception 객체에 담아서 넘겨 줘야 한다.
로그의 가장 중요한 역할은 오류의 발생원인을 빠른 시간 안에 찾도록 돕는 것이다. 로그 없는 오류는 해결할 방법이 없다.

공통모듈

요구사항에 따라 프레임워크에 Thread Local이나 Interceptor 등의 기능이 필요할 수도 있다. 개발프레임워크에서 중요한 요소 중의 하나가 반복되는 작업을 잘 녹여 넣어서 실제 업무코드에는 업무로직만 보이게 하는 것이다. 그러므로, 반복되는 작업을 잘 선별해서 프레임워크 안에 잘 숨겨 놓으면 개발자들의 개발공수도 줄어 들고, 반복되는 작업에 변경이 필요할 경우에도 모든 코드를 찾아서 고칠 필요 없이 프레임워크 코드 하나만 수정하면 간단하게 해결될 수 있다.

Interface 사용

그리고, 중요한 한 가지가 Service나 DAO 맨앞에 Interface 등을 통해 유연성을 제공하느냐 하는 것이다. 이것은 고객의 요구보다는 주로 SWA의 취향에 따라 결정되는데, Interface를 두는 것이 유연성이 좋아 보이긴 한데 나는 최대한 단순한 구조를 좋아하기 때문에 Interface 없는 구조로 설계한다. 이 방식이 실행속도도 빠르고, 가장 중요한 것은 개발속도가 빠르다는 것이다. SWA의 가장 중요한 임무 중에 하나가 개발자가 빠르게 개발하도록 지원하는 것인데 이것 하나 줄이는 것이 개발자의 스트레스를 줄여 주고 그만큼 일도 줄여 주기 때문이다. (SWA 입장에선 interface 하나가 별 게 아닌 것 같지만, 개발자들은 수십, 수백개를 만들어야 하기 때문에 굉장히 싫어한다.) 물론 이 부분도 각자 취향에 맞게 구성하는 것이라 굳이 한다면 말릴 방법은 없다.

 

개발 프레임워크 설계를 했으니 다음으로 프레임워크를 구현하기 위한 개발환경을 구성하겠다.

 

1. 소프트웨어 아키텍트란?

2. 요구사항 조사

3. 소프트웨어 아키텍처 설계

4. 개발 프레임워크 설계

5. 로컬 개발환경 구성

6. 개발프레임워크 준비

7. 개발가이드 작성 및 개발자 교육

8. 개발단계에서의 개발자 지원