Framework란 소프트웨어의 구체적인 부분에 해당하는 설계와 구현을 재사용이 가능하게끔 일련의 협업화된 형태로 클래스들을 제공하는 것이라 정의하고 있다.
쉽게 말하자면, Frame의 대표적인 의미는 틀, 구조이다. Frame은 어떤 대상의 큰 틀이나 외형적인 구조를 의미하는데, 프로그래밍의 Frame도 비슷한 의미를 갖는다.
하나의 애플리케이션을 건물이라 한다면, Frame은 위의 사진과 같은 건물의 뼈대이다.
소프트웨어 관점에서의 Framework는 어떠한 애플리케이션을 만들기 위한 틀을 제공한다고 이해할 수 있다.
Java를 공부하며 이미 Collections Framework를 배웠었다.
Java에서 자주 사용하는 Map, Set, List 등의 Collection들은 데이터를 저장하기 위해 널리 알려진 자료구조를 바탕으로 비슷한 유형의 데이터들을 가공하여 처리하기 쉽도록 표준화된 방법을 제공하는 클래스의 집합이다.
왜 Collection에 Framework라는 이름을 붙였을까?
Java 클래스의 유형 중, 기본적인 뼈대로만 구성되어 있는 것은 추상 메서드만 정의되어 있는 인터페이스다. 그리고 Java의 Collection은 Map, Set, List 같은 인터페이스와 그 인터페이스들을 구현한 구현체들의 집합이다.
결론적으로 프로그래밍에서의 Framework는 기본적으로 프로그래밍을 하기 위한 어떠한 틀이나 구조를 제공한다는 것이다.
효율적으로 코드를 작성할 수 있다.
기본적인 구조가 만들어진 상태에서 코드를 작성하는 것과, 아무것도 없는 상황에서 코드를 작성하는 것은 많은 차이가 있을 것이다.
개발하고자 하는 애플리케이션을 일일이 전부 개발하지 않고, 서로 다른 애플리케이션 간의 통신이나, 데이터를 데이터 저장소에 저장하는 등 다양한 기능들을 Framework가 라이브러리 형태로 제공함으로 개발자가 애플리케이션의 핵심 로직을 개발하는데 집중할 수 있게 한다.
정해진 규약으로 애플리케이션을 효율적으로 관리할 수 있다.
유지보수가 필요한 경우 더 빠르고 쉽게 수정할 수 있다.
내가 작업했던 코드를 다른 사람이 수정할 때도 Framework 규약대로 작성된 코드이므로 빠르게 코드를 파악해 수정할 수 있다.
유지보수 외에도 비슷한 기능을 개발할 때 코드의 재사용이 용이하며 기능의 확장도 쉽게 가능하다.
사용하고자 하는 Framework에 대한 학습이 필요하다.
Framework에서 정하는 규약들을 학습할 시간이 필요하다. Spring의 경우 Java에 대한 이해도 필요하지만, Spring이라는 Framework에 대한 학습이 추가적으로 필요하다.
자유롭고 유연한 개발이 어렵다.
사용하는 Framework의 규약을 벗어나기 어렵다.
위에서 건물의 구조로 예시를 들었었는데, 구조 자체를 변경하게 될 경우 이미 만들어진 틀을 모두 허물고 새롭게 만들어야 할 것이다. 이미 만들어진 애플리케이션에서 Framework를 변경하거나, 유연한 개발을 위해 Framework를 사용하지 않게 변경할 경우 정말 많은 시간과 노력이 필요할 것이다.
Library는 애플리케이션을 개발하는데 사용되는 일련의 데이터 및 프로그래밍 코드다.
Library의 사전적 의미는 도서관인데, 도서관은 여러 책들을 모아놓은 공간(집합체)라고 할 수 있다. 소프트웨어 관점에서의 Library도 애플리케이션을 개발할 때 필요한 기능을 미리 구현해놓은 집합체이다.
자동차로 예시를 들어보자. 자동차를 만들기 위해 필요한 것은 무엇이 있을까?
차체를 구성하는 Frame과 바퀴, 핸들, 엔진 등과 같은 다양한 부품들이 모여서 하나의 자동차를 이루고 있을 것이다.
많은 요소에서 Framework는 자동차의 Frame을 의미하며, Library는 자동차에서 다양한 기능을 제공하는 부품들이다.
자동차를 구매한 후, 부품은 쉽게 교체할 수 있지만 Frame은 그렇지 않다. 새로운 자동차를 사지 않는 이상, Frame을 교체하기란 어려운 일이다.
소프트웨어의 관점에서도 Framework를 교체하는 일은 어렵지만 Library는 쉽게 교체가 가능해 필요한 Library를 선택적으로 사용할 수 있다. 이를 명료하게 표현한다면 애플리케이션에 대한 제어권의 차이가 있다고 할 수 있다.
Library는 애플리케이션의 흐름의 주도권이 개발자에게 있지만, Framework는 애플리케이션의 흐름의 주도권이 Framework에 있다.
대부분의 기업들이 Framework를 선택할 때, 개발 생산성을 높이고 어떻게 하면 애플리케이션의 유지보수를 더 용이하게 할 것인지에 많은 초점을 맞추고 있다.
다양한 Framework 중에서 Spring Framework는 개발 생산성을 향상시키고 애플리케이션의 유지보수를 용이하게 하는 Framework의 기본 목적 그 이상을 달성할 수 있게 한다.
Spring Framework를 학습함으로써 객체 지향 설계 원칙에 잘 맞는 재사용과 확장이 가능한 애플리케이션 개발 스킬을 향상시킬 수 있다. 또한 보다 나은 성능과 서비스의 안전성이 필요한 복잡한 기업용 엔터프라이즈 시스템을 제대로 구축하기 위한 능력을 기를 수 있다.
Spring Framework가 도입되기 전에는 JSP나 Servlet 기술을 사용해 Java 웹 애플리케이션을 제작했고 한다. Spring MVC 방식이 도입되면서 Java 웹 애플리케이션의 제작 방식이 획기적으로 변하게 되었으며, Spring MVC 설정의 복잡함과 어려움을 극복하기 위해 Spring Boot가 탄생했다.
위 그림은 Spring 삼각형으로, Spring의 핵심 개념을 모두 표현하고 있다. POJO를 Ioc/DI, AOP, PSA를 통해 달성할 수 있다는 것을 의미한다.
POJO는 Plan Old Java Object이다. Plan이라 함은 플레인 요거트가 떠오른다. 플레인 요거트란 설탕이나 과일, 시리얼 등을 아무것도 넣지 않은 요거트를 말한다.
POJO의 PO도 마찬가지로 Java로 생성하는 순수한 객체를 의미한다.
POJO 프로그래밍?
POJO 프로그래밍이란 POJO를 이용해 프로그래밍 코드를 작성하는 것이다. POJO 프로그래밍으로 작성된 코드라고 하기 위해선, 두 가지의 기본적인 규칙을 지켜야 한다.
POJO 프로그래밍이 필요한 이유
Library는 애플리케이션 흐름의 주도권이 개발자에게 있고, Framework은 애플리케이션 흐름의 주도권이 Framework에 있다고 했었다.
애플리케이션 흐름의 주도권이 뒤바뀐 것을 IoC라고 한다.
Java 콘솔 애플리케이션의 경우 main() 메서드가 종료되면 애플리케이션의 실행이 종료된다.
웹에서 동작하는 애플리케이션의 경우, 클라이언트가 외부에서 접속해 사용하는 서비스이기 때문에 main()
메서드가 종료되지 않아야 한다.
서블릿 컨테이너에는 서블릿 사양에 맞게 작성된 서블릿 클래스만 존재하고, 별도의 main()
메서드가 존재하지 않는다.
main()
메서드처럼 애플리케이션이 시작되는 지점을 엔트리 포인트라고 한다.
서블릿 컨테이너의 경우, 클라이언트의 요청이 들어올 때마다 서블릿 컨테이너 내의 컨테이너 로직(service() 메서드)이 서블릿을 직접 실행시켜 주기 때문에 main() 메서드가 필요없다.
이 경우, 서블릿 컨테이너가 서블릿을 제어하고 있으므로, 애플리케이션의 주도권은 서블릿 컨테이너에 있다. 서블릭과 웹 애플리케이션 간에 IoC의 개념이 적용되어 있는 것이다.
IoC는 서버 컨테이너 기술, 객체 지향 설계 등에 적용하는 일반적인 개념인데 DI는 IoC의 개념을 구체화시킨 것이라 할 수 있다.
DI는 의존성 주입이라 해석할 수 있다.
A 클래스에서 B 클래스의 기능을 사용하기 위해 구현되어 있는 어떤 메서드를 호출하는 상황이라 가정해보자.
위 그림은 A 클래스가 B 클래스의 기능을 사용하는 것을 클래스 다이어그램을 표현한 것이다. 그림처럼 A 클래스가 B 클래스의 기능을 사용할 때, A 클래스는 B 클래스에 의존한다
고 표현한다.
의존성 주입이 필요한 이유
현재 클래스 내부에서 외부 클래스의 객체를 생성하기 위한 new
키워드를 사용할지 말지를 항상 염두에 두어야 한다.
애플리케이션 코드 내부에서 직접적으로 new
키워드를 사용할 경우, 객체지향 설계의 관점에서 중요한 문제가 발생할 수 있다.
만약 요구 사항이 변경되어 이미 작성한 코드를 수정해야 할 때, 수정할 코드가 수백군데라면?
수동으로 하나하나 수정하다보면 버그가 발생할 수도 있고, 테스트 코드를 다시 짜야할 수도 있다.
결국, new
키워드를 사용해 객체를 생성하게 되면 참조 할 클래스가 바뀔 경우 이 클래스를 사용하는 모든 클래스를 전부 수정해야한다. 이를 강하게 결합(Tight Coupling)되어 있다고 표현한다.
의존성 주입을 하더라도, 클래스들 간의 강한 결합은 피하고 느슨한 결합(Loose Coupling)을 사용하는 것이 좋다.
느슨한 의존성 주입은 어떻게 할까?
Java에서 클래스들 간의 관계를 느슨하게 만드는 대표적인 방법은 인터페이스(Interface)를 사용하는 것이다.
어떤 클래스가 인터페이스 같이 일반화된 구성 요소에 의존하고 있을 때, 클래스들 간에 느슨하게 결합(Loose Coupling)되어 있다고 한다.
인터페이스 타입의 변수에 그 인터페이스의 구현 객체를 할당할 수 있는데, 이를 업캐스팅(Upcasting)이라 한다. 업캐스팅을 통한 의존성 주입으로 느슨한 결합 관계를 유지할 수 있다.
※ Spring에서는 애플리케이션 코드에서 이우어지는 의존성 주입을 Spring에서 대신 해준다.
AOP는 관심 지향 프로그래밍이라 해석할 수 있다.
OOP는 객체 지향 프로그래밍으로, 객체 간의 관계를 지향하는 프로그래밍 방식이다.
사과를 먹기 위한 준비 과정은 사람마다 다를 수 있다. 어떤 사람은 사과의 껍질을 깎지 않고 먹기도 하지만, 어떤 사람은 사과의 껍질을 깎아 먹기도 한다. 또, 사과를 쪼갠 후 껍질을 깎는 사람도 있으며, 사과를 쪼개지 않고 껍질부터 깎는 사람도 있다.
준비 과정은 다를 수 있지만, 공통적인 부분이 존재한다. 위 사람들의 공통된 관심사는 바로, 사과를 먹는 것이다.
AOP에서의 Aspect는 위 예시와 마찬가지로 애플리케이션에 필요한 기능 중에서 공통적으로 적용되는 기능에 대한 관심과 관련이 있다.
AOP라는 것은 애플리케이션의 핵심 업무 로직에서 로깅이나 보안, 트랜잭션 같은 공통 기능 로직들을 분리하는 것이라 생각하면 이해하기 쉬울 것이다.
AOP가 필요한 이유
애플리케이션의 핵심 로직에서 공통 기능을 분리하는 이유는 아래와 같다.
로직에 공통적인 기능의 코드들이 이곳저곳에 보이면 코드 자체가 복잡해진다. 코드가 복잡해지면 버그가 발생할 가능성도 높아지고, 유지보수도 어려워질 수 있다.
만약 공통 기능에 대한 수정이 필요한 경우, 애플리케이션 전반에 적용되어 있는 코드들을 일일이 수정해야 하는 문제가 발생한다.
아래 문장은 애플리케이션을 제작하면서 기본적으로 가지면 좋은 사고이다.
어떻게 하면 코드를 깔끔하게 유지할 수 있을까?
어떻게 하면 중복되는 코드들을 재사용할 수 있을까?
추상화
위 그림은 피카소의 "소"라는 그림이다. 왼쪽에서 오른쪽으로 갈 수록 소의 모습이 단순화 되어 가는 것을 볼 수 있다.
피카소의 그림처럼 객체지향 프로그래밍에서는 어떤 클래스의 본질적인 특성만을 추출해 일반화 하는 것을 추상화라고 한다.
PSA가 필요한 이유
어떤 서비스를 이용하기 위한 접근 방식을 일관된 방식으로 유지함으로써 애플리케이션에서 사용하는 기술이 변경되어도 최소한의 변경만으로 변경된 요구사항을 반영하기 위함이다.
즉, PSA를 통해 애플리케이션의 요구사항 변경에 유연하게 대처할 수 있다.