2022. 1. 17. 01:35ㆍ백엔드
오늘을 시작으로 같이 공부하는 친구들과 자신이 공부한 내용 혹은 코드를 짠 내용을 블로그에 포스팅하기도 했다.
이렇게 포스팅을 하게 되어, 약간 설레기도 하고 제발 이렇게 정리하는 버릇이 오래갔으면 하는 두려움도 공존한다.
어찌 됐든! 처음으로 글을 써 내려가게 된 분야는 바로 Spring Framework이다!
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢
www.inflearn.com
위 링크에 있는 강의를 바탕으로 공부한 내용을 정리해보려고 한다.
우선 Spring에 대해서 간략하게 아는 바를 정리해보자면, Spring은 JAVA 진영의 백엔드 프레임워크이다. 최근에 많은 회사에서 각광받고 있는 것으로 알고 있다.
Spring을 통해 데이터 베이스에 데이터를 저장하거나 혹은 저장되어 있는 데이터를 사용자가 접근하고자 할 때, Spring Framework가 그 일련의 과정을 더욱 효율적으로 이루어지도록 도와주는 구조이다.
1. 스프링의 역사
자바 진영에서는 과거 EJB 기술이 있었는데, 이 기술이 물론 엄청나게 혁신적인 기술이었다는 것을 자명했다. (ex. 네트워크 분산 시스템..)하지만 엄청난 혁신성에도 불구하고 너무 높은 난이도와 필요로 하는 비용으로 인해 많은 개발자들이 죽어나가고 있었다고 한다.
늘 그렇듯 몇몇 개발자들은 "이정도면 내가 하는 게 더 쉽겠다 ㅋㅋ" 를 시전 하였는데, 그중에서 정말 실천력이 있던 몇몇 개발자분들(ex. 로드 존슨)이 스프링의 초기 모델과 Hibernate를 만들어내며 실제로 EJB와 비슷한 기능을 하지만 훨씬 더 쉬운 프레임워크를 만들어냈다.
그리고 그렇게 전설은 시작되었다..
여튼 결국 EJB 엔티티 빈 기술은 하이버네이트로 대체되었고, 결국 자바 진영에서는 하이버네이트를 아예 표준으로 삼아 JPA(Java Persistence API)를 확립하였고 JPA는 오늘날 표준 인터페이스가 되었으며, 자바에서 사용하는 ORM의 대부분을 JPA가 차지하게 되었다. 앞서 언급되었던 하이버네이트는 이 JPA의 많은 구현체들 중 하나인데, 이 구현체들 가운데 가장 높은 지분을 차지하고 있다고 한다.
개발자들에게 겨울이 지나가고 새로운 시작이 찾아왔다.
위와 같은 의미로 이름을 'Spring'이라고 지었다고 한다.
2. 스프링이란?
1. 스프링 DI 컨테이너 기술
2. 스프링 프레임워크
3. 스프링 부트, 스프링 프레임워크 등을 모두 포함한 스프링 생태계
스프링이라는 어휘는 통상적으로 위의 세 가지 중에 한 가지를 일컫는 말이다.
첫 번째, 스프링 DI 컨테이너 기술은 이 강의를 통해서 알게 될 내용이라 이번 글에서는 생략하도록 하겠다.
두 번째, 스프링 프레임워크는 한마디로 정말 "~~ 기능을 구현하려고 하는데 어떤 게 유리할까?"라는 질문에 대답하는 용도라고 생각하면 될듯하다. 스프링 프레임워크의 핵심 기술에 대해서 언급하고 넘어가자면, 스프링 DI 컨테이너 기술, AOP, 이벤트 등이 있다.
세 번째, 스프링 생태계라고 함은 스프링 프레임워크 뿐만 아니라, 스프링 부트, 스프링 데이터, 스프링 세션, 스프링 시큐리티 등 .. 스프링 자체에게 제공하는 많은 기능들을 포함하여 일컫는 말이다.
하지만 개발자들, 특히나 백엔드 개발자들에게 있어서 스프링의 진짜 핵심은 무엇일까?
- 스프링은 JAVA 언어 기반의 프레임워크
- 그러면 자바는? 객체 지향 언어!
- 그러면 스프링은 객체 지향의 특징을 활용하겠네?
위의 논리 전개 방식처럼 스프링은 결국 자바 언어 기반의 프레임워크이고 특히 "객체 지향" 어플리케이션을 효율적으로 개발할 수 있게 도와주는 프레임워크이다! 이렇게 알고 있는 것이 구글이나 네이버에 쳤을때 나오는 열거식 정의보다는 개발자들에겐 더욱 유용한 정보가 아닐까 싶다..ㅎ
스프링의 정의는 이 정도에서 마무리 짓도록 하겠다.
3. 그렇다면 좋은 객체 지향이란?
위에서 스프링은 객체 지향 언어인 자바를 기반으로 한 프레임워크라고 했고, 객체 지향의 특징을 잘 활용해서 효율적으로 개발할 수 있도록 해주었다고 했는데... 이쯤에서 드는 질문은 아마도 아래와 같을 것이다.
좋은 객체 지향이 뭐고, 그걸 어떻게 활용할 건데?
맞다. 나도 처음에 무작정 스프링을 공부할 때 어려웠던 이유는 너무 기능이 많았고 그것들을 깊지도 않게 수박 겉햝기식으로만 공부를 진행했기 때문이었다.
다시 본론으로 돌아가서 객체 지향 언어인 자바의 특징은 무엇일까?
나의 대학생활로 적용해보자면, 컴퓨터 프로그래밍(C언어 배움) 이후에 객체 지향 프로그래밍(JAVA 배움)에서 새롭게 배운 개념을 정리해볼 필요가 있다.
"추상화, 캡슐화, 상속, 다형성"
위 네가지 개념이 객프를 배우면서 가장 인상 깊게 배웠던 개념이었던 것 같다.
그렇다면 과연 진짜 객체를 지향하며 프로그래밍을 하려면 가장 중요한 개념은 무엇일까?
아마도 다형성을 꼽을 수 있을 것이다.
객체 지향 프로그래밍은 프로그램을 단순히 명령어의 목록으로 보는 것이 아니라 Object들간의 상호작용을 통해 프로그램이 실행된다는 것에 큰 의의를 갖고 있다.
그렇다면 각각의 Object들은 서로 대체가 가능해야 한다는 특징이 있다. 그러므로 다형성 개념이 가장 중요한 개념이라고 말할 수 있는 것이다!
세상을 역할과 실제로 구분해보자.
세상에는 수많은 역할이 있다. 예를 들면 프로그래밍을 하는 역할도 있을 것이고, 맛있는 음식을 하는 역할도 있을 것이다. 이러한 역할은 비단 사람에게만 적용되는 것이 아니다. 기록이 되는 것도 역할이 될 수 있고, 앉을 수 있는 것도 역할이 될 수 있고, 이동시켜주는 것도 역할이 될 수 있다.
그리고 실제를 위의 예시를 활용하여 열거해보자면 프로그래밍을 하는 역할을 수행하는 실제에는 나를 비롯한 많은 개발자들이 있을 것이고, 맛있는 음식을 하는 역할을 수행하는 실제에는 중화요리 전문가, 한식 전문가, 양식 전문가 등 수많은 요리사들이 포함되어 있을 것이다.
위의 예시에서 "역할"이라는 것은 좁은 개념으로 보자면 Interface와 같은 뜻이고, "실제"는 구현된 Class가 되는 것이다.
즉, 역할을 정하고 그 수행하는 실제를 구현하여 Object로 생성하는 것이 객체 지향 프로그래밍이라는 것이다!
역할을 수행할 수 있는 다른 무엇인가가 생긴다면 언제든 실제는 변경될 수 있는 것이다.
예를 들어, 모 기업에서 개발자가 사라졌을 경우 나를 고용함으로써 프로그래밍을 시킬 수 있다는 것이다 (얼른 취직했으면 ^_^)
객체들은 혼자 있지 않는다. 서로 협력 관계를 가질 수 있도록 해주자! 그것이 객체 지향 프로그래밍이다.
4. 좋은 객체 지향 설계를 위한 SOLID 원칙
SOLID 원칙은 SPR, OCP, LSP, ISP, DIP의 첫 글자를 따서 만든 원칙이다. 각각이 무슨 내용인지는 아래에서 정리하도록 하겠다.
- Single Responsibility Principle (SRP) : 단일 책임 원칙
- Open Closed Principle(OCP) : 개방 폐쇄 원칙
- Liskov Substitution Principle (LSP) : 리스코프 치환 원칙
- Interface Segregation Principle (ISP) : 인터페이스 분리 원칙
- Dependency Inversion Principle (DIP) : 의존관계 역전 원칙
1. Single Responsibility Principle (SRP) : 단일 책임 원칙
직역하자면 하나의 클래스는 하나의 책임만 가져야 한다는 뜻이다. 하지만, 책임이라는 것이 다소 모호한 감이 있다. '하나의 책임'이라는 것이 작다는 것을 강조하는 것을 의미하는 것도 아니고, 문맥에 따라 여러가지로 해석이 가능하다. 이때 중요한 기준은 바로 변경이다.
변경 사항이 생겼을 때 그것으로 인해서 수정해야 하는 부분이 적은 코드가 SRP를 잘 따른 코드라고 해석할 수 있다는 것이다.
예를 들어, 처음에 코드를 설계할때에 있어서 곧장 특정 데이터 베이스를 활용하는 것이 아니라, Local Memory를 활용하여 테스트를 해보고 이후에 코드에 문제가 없을 경우 데이터 베이스를 연동하여 불러오고자 한다고 하자. 만일 특정 클래스의 Method에서 직접 Local Memory에 접근하도록 코드를 작성할 경우 차후에 데이터 베이스를 연동하기 위해 코드를 수정할 경우 해당 Method가 사용된 모든 지점에서 코드 수정이 이루어져야 할 것이다. 하지만, 만일 저장소를 Interface를 활용하여 추상 Method로 작성해두었을 경우 구현체만 변경하여 코드를 작성하고 불러오는 Interface 구현체만 변경한다면 문제 없이 코드가 실행될 것이다. 이러한 코드가 SRP를 잘 따른 코드라고 해석할 수 있다.
2. Open Closed Principle(OCP) : 개방 폐쇄 원칙 (*중요한 원칙)
직역하자면 확장에는 열려 있으나 변경에는 닫혀 있도록 코드를 짜야한다는 뜻이다. 다소 추상적이고 모순적인 말이라고 느껴져서 필자도 크게 와닿지는 않았다. 다양한 사이트나 도서를 참고하여 나름대로 새롭게 정의를 내려 보았다.
Interface 자체를 변경하는 것은 지양하되, 해당 Interface를 통해 만들어지는 구현체는 확장이 가능하다.
그렇다면 구현체가 바뀔때마다 코드를 수정하는 것은 과연 OCP를 잘 따르는 코드라고 할 수 있을까?
이것에 대한 답은 '아니다' 이다. 물론 '덜' 변경하는 것이긴 하지만 궁극적으로 '안' 변경하는 것은 아닌 것이다. 그렇다면 스프링에서 이것에 대해 내놓은 해답은 무엇일까?
바로 객체를 생성하고 연관관계를 맺어주는 별도의 조립, 설정자인 Spring Container이다.
3. Liskov Substitution Principle (LSP) : 리스코프 치환 원칙
다른 원칙들에 비해서는 조금 직관적인 편이다. 요약하자면 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다는 것이다. 구체적으로 말하자면, 한 인터페이스의 특정 구현체에 짜여진 코드로 실행되는 Method를 통한 결과와 다른 구현체에서 실행되는 Method를 통한 결과는 같아야 한다는 것이다.
예를 들어, 자동차 자동기어와 관련된 인터페이스를 구현한다고 하자. P, R, N, D는 각각 주차, 후진, 중립, 주행의 기능을 갖고 있다고 하자. 이런 상황에서 기아, 쌍용, 테슬라, 벤츠에서 모든 자동차의 자동기어는 위와 같은 기능을 하도록 추상 Method를 구체화하였다. 그러던 어느날, Level이라는 자동차 회사에서 R에 기어를 두면 주행하고, D에 기어를 두면 후진을 하도록 추상 Method를 설계하였다. 그러면 Level 회사의 자동차를 운전하기 위해서 사용자들은 구현 Method가 다르다는 것을 인지해야하고 이를 교육받아야 한다. 이렇게 될 경우 LSP를 위반하였다고 하는 것이다.
LSP를 통해서 궁극적으로 추구하고자 하는 것은 '클라이언트의 편리함'이다. 단순히 코드가 컴파일이 되고, 안되고가 아니라 구현하고자 하는 규약이 명확하고 그 규약을 따라야 한다는 것이다.
4. Interface Segregation Principle (ISP) : 인터페이스 분리 원칙
다소 추상적으로 설명해보자면 '특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다' 라고 할 수 있다. 인터페이스는 추상적으로 기능을 담고 있는 것이긴 하지만 너무 많은 기능을 담고 있어서는 안된다는 것이다.
예를 들어, 수륙양용차가 개발되어 수륙양용차를 위한 인터페이스를 만들었다고 가정하자. 수륙양용차 인터페이스 안에는 육상에서 운전하기 위한 Method도 포함되어 있고, 수상에서 운전하기 위한 Method도 포함되어 있을 것이다. 그렇다면, 기존에 존재하던 모든 차량의 인터이스를 수륙양용차를 위한 인터페이스로 수정해야 할까? 이 질문의 답변 또한 '아니오' 이다. 오히려 수륙양용차를 위한 인터페이스를 구현할 것이 아니라, 수상 운송 수단과 육상 운송 수단을 위한 인터페이스를 구분하여 두 가지 인터페이스를 모두 상속받도록 하는 것이 효율이 더욱 좋은 코드라는 것이다.
정리하자면 추가적인 기능 혹은 두 가지 기술의 융합이 이루어졌을 경우 그 기능을 모두 포함한 인터페이스가 효율적인지 아니면 각각의 인터페이스를 생성하여 상속받도록 하는 것이 유리할지에 대해서 깊게 생각해보고 코드를 설계하라는 뜻이다.
5. Dependency Inversion Principle (DIP) : 의존관계 역전 원칙 (*중요한 원칙)
'추상화에 의존해야지, 구체화에 의존하면 안된다' 정도로 요약 할 수 있다. 위에서 예시를 들었던 것을 빌려서 비유하자면 '역할에 의존해야지, 실제에 의존하면 안된다' 라는 것이다. 더 범위를 좁혀서 객체 지향 언어에 대입해보자면 'Interface에 의존해야지, 구현체에 의존하면 안된다' 라는 뜻이다.
본질에 집중하라는 뜻이다! 저장소가 해야하는 일에 대해 생각하라는 것이다. 어떤 저장소를 사용할지에 대해서는 생각하면 안된다는 것이다.
기존 자바 문법 중 다형성을 이용하여 코드를 설계할 경우 어쩔 수 없이 DIP를 위반할 수 밖에 없는 경우가 발생하곤 한다. 이것을 위해서 Spring 은 어떠한 기능을 제공하고 있을까? 정답은 바로 DI(Dependency Injection)과 Spring Container 이다. 즉 스프링 요소들 간에 의존성을 주입함으로써 스프링 컨테이너 위의 각각의 요소들이 협업할 수 있도록 하는 것이다. 이후에 조금 더 구체적으로 이야기 해보도록 하자.
첫 번째 포스팅은 이 정도로 마무리 지어보려 한다. 이번 글에는 '왜 스프링이 생겨났는지?' 와 '그렇다면 어떤 코드가 좋은 코드인지?'에 대해서 공부한 내용을 다루어 보았다. 다소 추상적이기도 하고 실질적으로 코드가 들어간 부분은 거의 없었지만, 공부의 방향을 잡아주는데에 있어서 많은 도움이 되었다고 판단된다.
앞으로 프로젝트를 설계할 때, '역할'과 '실제'를 구분하여 설계하자. 그리고 각각의 '역할'을 조금이라도 더 객체 지향스럽게 설계하도록 노력한다면 분명 좋은 객체 지향적인 코드가 짜여질 것이다 ^_^
'백엔드' 카테고리의 다른 글
[ 스프링 공부 ] 컴포넌트 스캔 (0) | 2022.01.29 |
---|---|
[ 스프링 공부 ] 싱글톤 컨테이너 (0) | 2022.01.28 |
[ 스프링 공부 ] 스프링 컨테이너와 스프링 빈 (0) | 2022.01.27 |
[ 스프링 공부 ] 스프링 핵심 원리 이해하기 2 - 객체 지향 원리 적용 (0) | 2022.01.24 |
[ 스프링 공부 ] 스프링 핵심 원리 이해하기 1 (0) | 2022.01.21 |