스터디 시작
김영한 Spring 로드맵의 두 번째인 스프링 핵심 원리 - 기본편 강의를 보며 각자의 블로그에 정리하고 이를 교환 해 서로의 지식을 공유하기로 했다.
스프링(Spring)이란?
2002년, EJB(Enterprise Java Beans)를 사용하던 개발자 중 한 명인 로드 존슨(Rod Johnson)이 EJB의 문제점을 지적한 책을 출간하였다. EJB 없이도 고품질의 확장 가능한 애플리케이션 개발이 가능함을 보여주고 있어 책이 유명해졌고, 유겐 휠러(Juergen Hoeller)와 얀 카로프(Yann Caroff)가 로드 존슨에게 오픈소스 프로젝트를 제안하며 시작되었다고 한다. 스프링의 핵심 코드의 상당수는 현재도 유겐 휠러가 개발하고 있고, 여담으로 이름의 유래는 EJB(J2EE)라는 겨울을 넘어 새로운 시작(봄)
이라는 뜻으로 지었다고 한다.
spring.io 내 projects 화면을 켭쳐했다. 핵심이 되는 Spring Boot와 Spring Framework가 제일 위에 나오고, Spring Data, Cloud, Security 등 24개의 프로젝트(작성일 기준)가 존재한다.
Spring Boot
Spring Boot는 Spring Framework를 기반으로 빠르게 애플리케이션을 구축할 수 있는 프레임워크이다. Spring Boot는 클래스 패스와 빈 설정을 분석하여 필요한 구성 요소를 자동으로 추가하여 환경(Infrastructure) 설정에 대한 부담을 줄인다. 이를 통해 개발자는 비즈니스 로직에 집중할 수 있다. Spring Boot는 Starter Projects와 Auto-Configuration을 통해 쉽게 마이크로 서비스를 개발할 수 있도록 지원한다.
Spring Framework
Spring Framework는 Enterprise Java 애플리케이션을 구축하기 위한 오픈소스 프레임워크이다. AOP(Aspect-Oriented Programming), 스프링 DI 컨테이너(Dependency Injection) 같은 기술을 제공하여 복잡한 개발을 단순화한다. 또한 Spring Framework는 IoC(Inversion of Control) 컨테이너를 통해 Java 객체의 생명주기를 관리하고, Core 컨테이너는 Core, Beans, Context 모듈로 구성되어있다.
다른 프로젝트들
- Spring Data : 다양한 데이터베이스에 일관된 접근 방식을 제공한다.
- Spring Cloud : 분산 시스템에서 공통 패턴을 쉽게 구축할 수 있도록 도와준다.
- Spring Security : 인증 및 권한 부여를 관리하며, 보안 강화를 위해 사용한다.
- Spring Integration : 이벤트 기반 액션을 처리하는데 적합하며, 데이터 레이어 위에서 이벤트 캡쳐 및 처리가 가능하다.
강의에서는 핵심 개념을 이해하는 것이 해당 기술을 이해하고 사용하는데 가장 중요하다고 얘기한다. 이 기술을 왜 만들었는지? 이 기술의 핵심 컨셉이 무엇인지? 이다. 스프링은 Java 기반의 프레임워크로, Java의 가장 큰 특징으로는 객체 지향 언어인 것이다. 따라서 스프링은 객체 지향 언어가 가진 강력한 특징을 살려내어 좋은 객체 지향 애플리케이션을 개발할 수 있게 도와주는 프레임워크라는 것이 스프링의 핵심 개념이다.
좋은 객체 지향 프로그래밍이란?
우선 객체 지향 프로그래밍(OOP, Object-Oriented Programming)의 특징이다.
- 캡슐화(Encapsulation) : 데이터와 코드의 형태를 외부로부터 숨기고, 데이터의 구조와 역할, 기능을 하나의 단위로 묶는 방법이다. 이는 데이터의 무결성을 유지하고, 객체 간의 상호 작용을 명확히 정의한다.
- 추상화(Abstraction) : 복잡한 시스템의 핵심적인 특성만을 드러내고, 불필요한 세부 사항은 숨기는 방법이다. 이를 통해 시스템의 복잡성을 줄이고 이해하기 쉽게 만든다.
- 상속(Inheritance) : 새로운 클래스가 기존 클래스의 속성과 메서드를 물려받아 사용하는 기능이다. 이를 통해 코드의 재사용성을 높이고, 계층 구조를 형성할 수 있다.
- 다형성(Polymorphism) : 객체가 다양한 형태로 표현될 수 있는 능력이다. 이는 오버로딩(Overloading)과 오버라이딩(Overriding)을 통해 구현된다.
위 특성들을 바탕으로 객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만든다. 여기서 '유연하고 변경이 용이'는 레고를 조립하듯, 키보드를 교체하듯이 컴포넌트를 쉽고 유연하게 변경하면서 개발할 수 있는 방법을 말한다. 여기서 자연스럽게 특징 중 하나인 다형성(Polymorphism)에 집중하게 된다.
다형성의 실세계 비유
물론 실세계와 객체 지향을 1:1로 매칭하는 것을 불가하다. 하지만 역할과 구현으로 세상을 구분하면 다형성을 이해하기 보다 쉽다.
운전자 역할과 자동차 역할로 구분했을 때, 자동차 구현인 차A가 자동차 역할을 하든 차Z가 자동차 역할을 하든 운전자 역할에서 봤을 때는 자동차 역할이다. 어떤 자동차가 와도 운전을 할 수 있는 것이다. 이와 같이 역할과 구현으로 구분하면 세상이 단순해지고, 유연해지며 변경도 편리해진다. 장점으로는 클라이언트가 대상의 역할(인터페이스)만 알면 되고, 구현체의 내부 구조를 몰라도 되고, 내부 구조가 변경되어도 영향이 없으며, 구현체 자체를 변경해도 영향을 받지 않는다는 것이다.
Java의 다형성을 활용하면 역할은 곧 인터페이스(Interface)이며, 구현은 인터페이스를 통해 구현한 클래스나 구현 객체를 의미한다. 이는 객체를 설계할 때 역할과 구현을 명확히 분리해야 한다는 말로, 역할(인터페이스)을 먼저 부여하고 그 역할을 수행하는 구현 객체를 만들어야 한다. 혼자 있는 객체는 없으며 수많은 객체 클라이언트(요청)와 객체 서버(응답)는 서로 협력 관계를 가진다.
Java의 다형성
메서드 오버라이딩은 자식 클래스가 부모 클래스의 메서드를 동일한 이름, 매개변수, 반환 타입으로 재정의하는 것을 의미한다. 이를 통해 자식 클래스는 부모 클래스의 메서드와 같은 이름을 가지지만, 다른 구현을 제공할 수 있다. 이런 메서드 오버라이딩은 다형성을 구현하는 중요한 방법 중 하나이다.
입문 스터디 당시에 구현했던 MemberRepository이다. 이때도 MemberRepository 인터페이스를 통해 MemoryMemberRepository, JdbcMemberRepository 등 다양한 데이터 저장 방식으로 클래스를 구현했었다. MemberService가 클라이언트 역할이고 MemberRepository가 서버 역할일 때, 클라이언트를 변경하지 않고 서버의 기능을 유현하게 변경할 수 있었다.
이런들 저런들 역할과 구현이라는 컨셉을 다형성을 통해 객체 지향으로 구현할 수 있으며, 이는 유연하고 변경이 용이한, 확장 가능한 설계가 된다. 변경 시 클라이언트에 영향을 주지 않기 위해서는 인터페이스를 안정적으로 설계하는 것이 중요하다. 물론 인터페이스 자체가 변하게 되면 큰 변경이 발생한다는 한계도 있다.
SOLID 원칙
객체 지향 설계의 SOLID 원칙은 소프트웨어를 더 유지보수하기 쉽고 확장 가능한 구조로 만드는데 도음을 주는 다섯 가지 기본 원칙이다. SOLID는 SRP, OCP, LSP, ISP, DIP의 첫 글자를 따서 만든 약어이다.
- SRP(Single Responsibility Principle, 단일 책임 원칙)
- 클래스는 하나의 책임만을 가져야 한다. 즉, 클래스는 하나의 이유로만 변경되어야 한다.
- 하나의 책임이라는 것은 모호하나, 중요한 기준은 변경이다. 변경이 있을 때 파급 효과가 적으면 SRP 원칙을 잘 따른 것이다.
- OCP(Open-Closed Principle, 개방-폐쇄 원칙)
- 소프트웨어 요소(Entity)는 확장에는 열려 있어야 하지만 수정에는 닫혀 있어야 한다.
- 다형성, 위에서 얘기한 역할과 구현의 분리를 생각할 수 있다.
- 입문 스터디에서 구현했을 당시에는 MemberService가 구현 클래스를 직접하며 코드를 변경했었다.
- 다형성(인터페이스를 통한 클래스 구현)은 사용 했으나 OCP 원칙은 지키지 못했다.
- 객체를 생성하고 연관관계를 맺어주는 별도의 조립, 설정자가 필요하며 이는 추후에 배운다.
- LSP(Liskov Substitution Principle, 리스코프 치환 원칙)
- 부모 클래스의 객체는 자식 클래스의 객체로 대체될 수 있어야 한다.
- 위 자동차를 예로 들면 엑셀은 앞으로 가라는 기능인데, 뒤로 가게 구현하면 LSP 원칙 위반이다.
- 즉, 인터페이스로 구현한 클래스는 인터페이스에 정의된 규약은 다 지켜아한다.
- ISP(Interface Segregation Principle, 인터페이스 분리 원칙)
- 클라이언트는 사용하지 않는 인터페이스에 의존하지 않아야 한다.
- 클라이언트가 불필요한 메서드나 속성에 의존하지 않도록 하여 코드의 유지보수성과 유연성을 높이는 데 도움을 준다.
- 하나의 범용 인터페이스를 사용하기 보다는 여러 개의 특정한 인터페이스를 사용하는 것이 더 좋다.
- 클라이언트는 사용하지 않는 인터페이스에 의존하지 않아야 한다.
- DIP(Dependency Inversion Principle, 의존관계 역전 원칙)
- 고수준 모듈은 저수준 모듈에 의존하지 않아야 하며, 둘 다 추상화에 의존해야 한다.
- 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻이다.
- 위에서 얘기한 구현과 역할에서 역할에 의존하라는 말이다. 차A에 의존하게 되면 자동차를 바꾸기 어렵지만, 자동차에 의존하면 쉽게 바꿀 수 있다.
- OCP와 같이 MemberService는 인터페이스에 의존하지만, 구현 클래스도 의존하고 있다.
- 코드 내에서 직접 구현 클래스를 선택하기 때문에 DIP 원칙 위반이다.
- 고수준 모듈은 저수준 모듈에 의존하지 않아야 하며, 둘 다 추상화에 의존해야 한다.
객체 지향의 핵심은 다형성이지만, 다형성만으로는 OCP, DIP를 지킬 수 없다.
객체 지향 설계와 스프링
스프링은 스프링 DI 컨테이너를 통해 다형성을 유지하며 OCP, DIP를 준수할 수 있다. 스프링 DI 컨테이너는 객체 간 의존성을 런타임 시에 주입하는데, 이를 통해 인터페이스를 정의하고 다양한 구현체를 주입받기 때문이다. 이렇게 스프링으로 개발하게 되면 클라이언트 코드의 변경 없이 기능 확장이 가능하게 된다.
물론 이론적인 내용으로 DI를 완전히 이해하기는 어렵다. 이제 실제 코드를 작성해보면서 이해해야겠다.
'스터디 > Spring' 카테고리의 다른 글
Spring 스터디2 - 3 (0) | 2025.04.13 |
---|---|
Spring 스터디2 - 2 (0) | 2025.03.30 |
Spring 스터디 - 7 (1) | 2025.03.09 |
Spring 스터디 - 6 (0) | 2025.02.18 |
Spring 스터디 - 5 (0) | 2025.02.17 |