본문 바로가기

IT 살이/04. 기술 - 프로그래밍

개발 프레임워크 만들기 대장정 21 - Aspect Oriented Programming 개념 I

Aspect지향 프로그래밍! 프레임워크 입장에서는 아주 쓸모있고 중요한 개념이다. 개발자들의 코딩을 화~악 줄여줄 수 있고 또한 프로젝트가 진행하고 있는 도중에도 개발자들의 코드 수정없이 프레임워크단에서 갑의 요청 사항을 최대한 흡수해 줄 수 있는 완충 역할을 할 수 있는 방법이다.

그러나 얼른 와 닫지 않는 용어이다. Object Oriented Programming이라는 용어를 처음 들어을때도 이런 떨떠름한 기분이었을까 하는 생각이 든다. Object가 뭔지 정의를 정확히 내리라면 머뭇거리게 되지만, 그래도 우리는 이것에 대해 이해는 하고 있다. 문장의 주어 또는 목적어로 사용될 수 있는 "놈"들이다.  "이 녀석의 어떤 메소드를 호출하면 ..." 또는 "저 녀석의 어떤 메소드를 호출해줘야 ~ 할 수 있다"처럼 마치 이야기의 대상처럼 사용할 수 있는 것이 object이다.

그럼 aspect란? longman 사전을 찾아보면 다음처럼 정의되어 있다 : "one part of a situation, idea, plan etc that has many parts". 그리고는 다음과 같은 예문이 나와 있다 : "Dealing with people is the most important aspect of my work. 사람을 다루는 일이 내 일중의 가장 중요한 일이다". "전체중의 부분 또는 전체중의 단면"을 의미한다고 하겠다.

소프트웨어 개발에서의 aspect도 의미적으로는 이와 비슷한 개념으로 정리될 수 있을 것 같다. 비즈니스 로직을 실제로 구현하다보면 필요한 비즈니스 로직 구현외에도 기능성 코딩을 해야 하는 경우가 많다. 예를 들어 로깅이나 예외처리, 트랜잭션처리등은 비즈니스 요구와는 직접적인 상관은 없지만 계속 반복되는 기능들이다. 이런 기능들을 애플리케이션을 만들때마다 또는 하나의 애플리케이션에서 다른 비즈니스 로직을 구현할때마다 계속 반복해서 코딩하기 보다는 처리 모듈들을 "단면!"별로 분리해서 구현하자는 것인데, 이런 각각의 단면 모듈들(로깅, 예외처리, 트랜잭션처리, 보안처리등)을 aspect라 하고 있다.

비즈니스 계층의 메소드를 개발할때 다음과 같은 형식의 코딩에 대한 경험이 있을 것이다.

public void 메소드()

{

    // 메소드 시작 로깅

    // 메소드 호출

    // 메소드 종료 로깅

}

또는 다음과 같은 형식으로 트랜잭션을 처리해본 경험도 있을 것이다.

public void 메소드()

{

    // 트랜잭션 설정 및 시작

    try

    {

        // 비즈니스 로직 구현


    }

    catch

    {

        // 트랜잭션 롤백


    }

}

순수한 비즈니스 로직 구현 코드와 로깅, 트랜잭션 처리 코드가 섞여 있고 이런 부가적인 코드는 메소드마다 복사되어서 사용되었다. 이런 로깅 그리고 트랜잭션 처리 코드는 특정 비즈니스 로직에서만 사용되는 것이 아니다. 아래 그림에서처럼 계좌이체 모듈, 입출금모듈, 이자계산 모듈 등 여러 관심 모듈에 걸쳐서(cross) 공통적으로 필요한 모듈들이다. 따라서 aspect를 cross concerns이라는 용어로도 표현한다. 다음 그림에서는 로깅, 보안, 트랜잭션과 같은 cross concerns 구현하고자 하는 비즈니스 관심 모듈의 관계를 개념적으로 표현하고 있다.

(객체 지향을 넘어서 관점 지향으로 AOP. http://www.zdnet.co.kr/builder/dev/java/0,39031622,39147106,00.htm )

객체 지향에서는 객체를 분리해내고 그것을 설계하는 것이 중요하듯이 aspect 지향에서는 앞에서와 같은 cross concerns을 정의하고 분리해서 설계하는 것이 중요하다. 객체 지향으로 설계된 객체들을 구현하는 툴로서 C++, C#이 있듯이 aspect 지향으로 설계된 aspect들을 메인 로직과 혼합하는 작업을 가능하도록 하는 툴들이 있다. .NET계열에서는 Spring.NET이 그 대표적인 예라 하겠다. 그러나 aspect 자체를 구현한 코드는 객체 지향 언어를 사용해서 구현한다.

비즈니스 로직과 그것을 구현하기 위한 핵심 클래스 및 메소드는 객체 지향 설계(OOA)로 도출될 수 있다. 그러나 이런 aspect들은 이런 객체 지향 방법론으로 도출할 수 없었다. 그러니 계속해서 같은 목적( 로깅, 예외처리, 트랜잭션 처리)을 갖는 코드가 조금씩 변경되어서 copy&paste 방식으로 이곳 저곳에서 반복되어서 삽입될 수 밖에 없었지만, aspect지향의 컨셉과 그것을 구현할 수 있는 툴들의 제공으로 이제는 코드가 좀 더 깔끔하게, 좀 더 비즈니스 중심으로 될 수 있게 된것이다.

이쯤되면, AOP란 OOP를 대신하는 프로그래밍 기법이 아님을 인식할 수 있었으리라 본다. 오히려 OOP를 기본으로 하되 그것이 처리할 수 없는 부분을 보충해주는 프로그래밍 방법이라 하겠다.

"AOP를 구현한다"는 것은 "분리된 cross concerns을 실제로 코드로 구현하고 그 코드를 필요한 비즈니스 관심 모듈의 적절한 위치에 삽입하는 작업"이라고 할 수 있겠다. 이런 AOP 구현을 이해하기 위해서는 이해해야하는 하위 개념들이 있다. 이런 개념들은 조금은 낯선 용어들로 표현되고 있다.

advice(또는 interceptor)
advice가 바로 앞에서 말한 "단면"을 구현한 코드이다. 즉 로깅, 트랜잭션등을 구현한 코드를 말한다. 이것을 interceptor라고도 한다. 두 표현 모두 옆에서 치고 들어오는 것들 표현하고 있다. advice 즉 충고 또는 훈수라는 것도 옆에서 갑작스레 치고 들어오는 것은 마찬가지다. 메인 비즈니스 로직에 추가되어 부가적인 훈수를 두는 코드를 말한다.

joinpoint

advice가 치고 들어올 수 있는 포인트들이다. cross concerns 모듈의 메인 비즈니스 로직에 삽입이 가능한 후보 위치를 말한다. 비즈니스 로직을 구현한 메소드가 호출되기 전 또는 후, 반환값이 반환되기 전 , 예외가 던져지는 지점, 클래스가 초기화되는 곳, 필드를 액세스하는 부분등이 모두 advice가 삽입될 수 있는 후보 포인트들이다. 그러나 모든 jointpoint가 실제로 advice가 삽입되는 곳은 아니다.

pointcut

joinpoint중에서 실제로 advice가 적용될 위치를 나타낸다. joinpoint가 개념적인 것이라면 툴마다 실제로 구현하고 있는 pointcut은 다를 수 있다. 뒤에서 보게 되겠지만 특정 pointcut를 나타내는 타입들이 Spring.NET에도 이미 구현되어 있다.

advisor

pointcut + advice를 말한다. 즉 어디서(where, pointcut) 무슨 일(what, advice)이 일이 일어날지를 정의한다. advisor가 바로 aspect의 실제 구현된 모습이라고 할 수 있다.

advised object /advised method

문서를 보다 보면 advised된 객체 또는 메소드라는 말을 보게 된다. advice 코드가 삽입된, 적용된 객체 또는 메소드라는 의미이다. advisor에 의해 훈수를 받은 객체 또는 메소드라는 것이다.

AOP는 일반적인 프로그래밍 방법이다. 즉 Spring.NET만의 개념은 아니다.  Spring.NET에서는 이런 AOP 개념들을 모두 구현하기 위한 방법을 제공하고 있지만, Spring.NET의 IoC컨테이너는 이 AOP 기술에 의존하고 있지는 않다. 즉 Spring.NET 사용자는 원한다면 AOP를 사용하지 않아도 된다는 것이다.

그러나 반복되는 코드를 단지 어트리뷰트를 사용해서 선언적(declarative)인 방식으로 해결할 수 있다면 코드가 깔끔해질 수 있을 것이고 유지, 보수에도 효과적인 방법이 될 수 있을 것이다. Spring.NET에서는 AOP를 구현할 수 있는 모든 준비를 갖춰놓고 있다. 사용자는 이제 HOW-TO만 배우면 되는 것이다.

나중에 알게 되겠지만, Spring.NET에서 AOP 개념은 프락시를 이용하고 있다. 그리고 프락시에 대한 소유권은 프레임워크에서 가지게 된다. 개발자가 타겟 객체를 요구할때 프레임워크에서는 그 객체에 대한 프락시를 반환하는 패턴을 이용하게 된다. 이 프락시를 잘 이용하면 개발자의 일명 삽질이 상당히 줄어들 수 있다.

프락시 코드를 프레임워크단이 가진다는 의미는 타겟 객체의 메소드 호출을 프레임워크단에서 모두 catch할 수 있다는 것인다. 즉 실제로 타겟 객체의 메소드 호출을 수행하기 전에 그리고 메소드 호출을 수행하고 나서의 순간들을 모두 프레임워크에서 포착할 수 있게 되어 필요한 작업을 할 수 있다. 필요한 작업이란 예를 들어 타겟 메소드를 호출하기 전에 타이머를 실행시켜 놓은 다음 타겟 메소드의 호출이 종료된 후 타이머의 시간을 재서 메소드의 실행 시간을 체크할 수도 있다는 것이다. 또 다른 예로 프락시를 통해서 타겟 메소드에 대한 정보를 얻어서 적절한 로그를 남기는 작업을 프레임워크단에서 처리할 수도 있다. 이런 작업들이 개발자들의 코드 수정없이 프레임워크단에서 일괄적으로 처리될 수 있다는 것이다. 즉 프락시 패턴을 이용하게 되면 프로젝트 진행 도중에 비즈니스 로직과 상관없는 추가 요구 사항은 최대한 프레임워크단에서 커버할 수 있는 구조가 된다는 것이다.

다음 포스트에서는 AOP 관련 샘플을 통해서 Spring.NET이 지원하는 방법을 알아보겠다. 앞에서 보이지 못한 IoC 예제 코드 즉 객체를 등록하고 설정하는 방법에 대한 것도 이 예제에서 함께 설명하도록 하겠다.