AOP란? (Aspect-Oriented Prgramming, 관점 지향 프로그래밍)
AOP 프레임워크는 애플리케이션에서 사용되는 기능을 재사용하고, 다른 관점을 추가해 클래스나 메소드를 수정하지 않고 추가하고자 하는 동작을 추가할 수 있도록 도와주는 프레임워크입니다.
AOP는 OOP(객체 지향 프로그래밍)이 가지고 있는 기능에서 새로운 관점(다른 측면)으로 공통된 요소를 추출해서 재사용합니다.
대표적으로 AOP 프로그래밍은 로깅, 유효성 검사, 트랜잭션 처리, 인증/보안에서 활용됩니다.
예를 들자면, 모든 애플리케이션은 아래와 같은 요구사항을 만족할 필요가 생길 수가 있습니다.
- 응용 프로그램에는 쿼리 또는 업데이트 전에 사용되는 인증 시스템이 있어야 합니다.
- 데이터는 데이터베이스에 쓰기 전에 유효성을 검사해야 합니다.
- 애플리케이션에는 적절한 작업을위한 감사 및 로깅이 있어야 합니다.
- 응용 프로그램은 작업이 정상인지 확인하기 위해 디버깅 로그를 유지해야 합니다.
- 일부 작업은 원하는 범위에 있는지 확인하기 위해 성능을 측정해야 합니다.
이러한 요구사항을 만족하기 위해서는 반복적인 코드가 들어가기 마련입니다.
동일한 코드가 반복되는 것은 DRY 원칙에 위배되면서 유지보수를 어렵게 만드는 원인이 되어버립니다.
이때, AOP 관점 지향 프로그래밍을 이용해서 일반적인 코드를 Aspect 측면과 분리해서 작성합니다.
위의 그림에서 3개의 화살표를 각각의 메서드라고 가정합니다.
A,B,C이고, 출금, 입금, 계좌 조회라고 가정하죠. 은행에서 사용하는 기본적인 기능들입니다.
출금을 예로 들었을 때, 출금을 할 때 사용자에게 금액을 출금하는 상황을 보여주기 위한 Log(Logging)을 출력해주고, 출금을 하는 사용자의 정보에 대한 보안(Security)처리를 진행하고, 처리가 끝나면 Transaction을 반영해야겠죠?
이러한 일련의 과정이 출금만이 해당하는 것이 아니라 B와 C, 입금과 계좌조회도 해당하는 사항입니다.
이때 이러한 절차를 반복하는 코드가 생긴다면 유지보수를 진행하는데에 있어서 굉장히 불편해지겠죠. 그래서 아래와 같은 형태로 객체들을 묶습니다.
위와 같은 OOP 구조에서는 객체들이 다른 객체를 참조하는 형태로 설계가 되어 있는데,
하지만 반대로 AOP 구조에서는 Aspect(Logging, Security)에 공통된 기능을 상속받은 형태로 설계가 되는 것이지요.
AOP는 아래와 같은 형태로 사용할 수 있습니다.
- 애플리케이션에서 로깅을 구현합니다.
- 작업 전에 인증 사용 (예 : 인증 된 사용자에 대해서만 일부 작업 허용).
- 속성 설정자에 대한 유효성 검사 또는 알림 구현 (INotifyPropertyChanged 인터페이스를 구현하는 클래스에 대한 속성이 변경된 경우 PropertyChanged 이벤트 호출)
- 일부 메서드의 동작 변경.
그 다음은 AOP에서 사용되는 용어들입니다.
Aspect란 기존 객체의 핵심 기능에 추가가 되어 의미를 가지는 객체 모듈입니다. 이러한 Aspect는 기존 기능에 추가 기능을 정의하는 어드바이스와, 어드바이스를 적용하는 장소를 결정하는 포인트컷을 가지고 있습니다.
어드바이스란, 실질적으로 어떤 일을 할 것인지, 언제 할 것인지 등 부가 기능을 담은 구현체입니다.
포인트컷(Point)이란 부가적용이 적용될 대상을 선정하는 방법입니다.
Target Object는 Aspect를 적용하는 곳을 말합니다. 앞에 있는 출금, 입금, 계좌조회 같은 클래스나 메서드가 이에 해당합니다.
AOP 프레임워크에서 사용되는 중요한 디자인 패턴은 데코레이터 패턴과 프록시 패턴 2가지가 있습니다. 이에 대한 개념이 숙지가 되어있어야 AOP 프레임워크를 자유롭게 사용할 수 있습니다.
데코레이터 패턴 사용 예
레포지토리 인터페이스
public interface IRepository<T>
{
void Add(T entity);
void Delete(T entity);
void Update(T entity);
IEnumerable<T> GetAll();
T GetById(int id);
}
레포지토리 인터페이스 구현
public class Repository<T> : IRepository<T>
{
public void Add(T entity)
{
Console.WriteLine("Adding {0}", entity);
}
public void Delete(T entity)
{
Console.WriteLine("Deleting {0}", entity);
}
public void Update(T entity)
{
Console.WriteLine("Updating {0}", entity);
}
public IEnumerable<T> GetAll()
{
Console.WriteLine("Getting entities");
return null;
}
public T GetById(int id)
{
Console.WriteLine("Getting entity {0}", id);
return default(T);
}
}
레포지토리를 사용할 클래스
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
로깅이 없는 프로그램 실행
static void Main(string[] args)
{
Console.WriteLine("***\r\n Begin program - no logging\r\n");
IRepository<Customer> customerRepository =
new Repository<Customer>();
var customer = new Customer
{
Id = 1,
Name = "Customer 1",
Address = "Address 1"
};
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine("\r\nEnd program - no logging\r\n***");
Console.ReadLine();
}
결과
레포지토리를 구현한 로거 레포지토리 구현
public class LoggerRepository<T> : IRepository<T>
{
private readonly IRepository<T> _decorated;
public LoggerRepository(IRepository<T> decorated)
{
_decorated = decorated;
}
private void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg, arg);
Console.ResetColor();
}
public void Add(T entity)
{
Log("In decorator - Before Adding {0}", entity);
_decorated.Add(entity);
Log("In decorator - After Adding {0}", entity);
}
public void Delete(T entity)
{
Log("In decorator - Before Deleting {0}", entity);
_decorated.Delete(entity);
Log("In decorator - After Deleting {0}", entity);
}
public void Update(T entity)
{
Log("In decorator - Before Updating {0}", entity);
_decorated.Update(entity);
Log("In decorator - After Updating {0}", entity);
}
public IEnumerable<T> GetAll()
{
Log("In decorator - Before Getting Entities");
var result = _decorated.GetAll();
Log("In decorator - After Getting Entities");
return result;
}
public T GetById(int id)
{
Log("In decorator - Before Getting Entity {0}", id);
var result = _decorated.GetById(id);
Log("In decorator - After Getting Entity {0}", id);
return result;
}
}
위 로거 레포지토리는 데코레이팅된 클래스의 메서드를 래핑(Wrapping)하고 로깅 기능을 추가합니다.
기존의 로깅이 적용되지 않은 메인에서 로깅을 적용한 메인
static void Main(string[] args)
{
Console.WriteLine("***\r\n Begin program - logging with decorator\r\n");
// IRepository<Customer> customerRepository =
// new Repository<Customer>();
IRepository<Customer> customerRepository =
new LoggerRepository<Customer>(new Repository<Customer>());
var customer = new Customer
{
Id = 1,
Name = "Customer 1",
Address = "Address 1"
};
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine("\r\nEnd program - logging with decorator\r\n***");
Console.ReadLine();
}
결과
이처럼 기존 애플리케이션에서 공통된 기능을 추려내어 Aspect로 분리해서 사용하는 것으로 재사용성을 높이고 유지보수를 효율적으로 할 수 있게 만드는 것이 AOP 프로그래밍 입니다.
'Backend&Devops > 프레임워크&아키텍처' 카테고리의 다른 글
서비스 지향 아키텍처란? (SOA, Service Oriented Architecture) (0) | 2022.05.11 |
---|