[Spring] 좋은 객체 지향 설계의 5가지 원칙(SOLID)대로 개발하기

사이드 프로젝트 개발을 본격적으로 들어가기 전, 객체 지향 설계를 위해서는 고려해야하는 SOLID 중 가장 중요한 DIP에 대해서 정리한다.
그리고 정리한 개념을 기억하며 순수 Java로 Spring의 dependency 설정 도움 없이 구현을 해보고자 한다.
이 과정을 통해 왜 Spring을 사용해야 하는지 알게 된 상태에서 Spring Boot 사용을 통해 개발하고자 함이 목적이다.

좋은 객체 지향 설계의 5가지 원칙(SOLID)

DIP 의존 관계 역전 원칙(Dependency Inversion Principle)

  • 의존 관계 역전 원칙은 SOILD에서 가장 중요한 원칙 중 하나이다.
  • 프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다.” 의존성 주입은 이 원칙을 따르는 방법 중 하나다.
    • Client Code가 구현체를 바라보는게 아니라 Interface만 바라봐라
  • 즉, 역할에 의존해라
  • 그런데 OCP에서 설명한 MemberService는 인터페이스에 의존하지만, 구현 클래스도 동시에 의존한다.

정리

  • 객체 지향의 핵심은 다형성
  • 다형성 만으로는 쉽게 부품을 갈아 끼우듯이 개발할 수 없다.
  • 다형성 만으로는 구현 객체를 변경할 때 클라이언트 코드도 함께 변경된다.
  • 다형성 만으로는 OCP, DIP를 지킬 수 없다.
  • 뭔가 더 필요하다.

객체 지향 설계와 스프링

Spring을 공부하다보면 왜 이렇게 객체 지향의 중요성에 대해 많이 이야기가 나오는 것일까? 그것은 스프링은 다음 기술로 다형성과 OCP, DIP를 가능하게 지원하기 때문이다.

  • DI(Dependency injection) : 의존관계, 의존성 주입
  • DI 컨테이너 제공 : 자바 객체들을 컨테이너 안에 넣어어놓고 그 안에서 의존관계를 주입해주는 역할을 한다. 이것들을 활용하면 클라이언트의 변경 없이 개발이 가능하다.

정리

  • 모든 설계에 역할과 구현을 분리하자
    • 자동차, 공연의 예를 떠올려보자
  • 어플리케이션 설계도 공연을 설계하듯이 배역만 만들어두고, 배우는 언제든지 유연하게 변경할 수 있도록 만드는 것이 좋은 객체 지향 설계다.
  • 이상적으로는 모든 설계에 인터페이스를 부여하자.

하지만 실무적으로 고민해보자.

  • 인터페이스를 도입하면 추상화라는 비용이 발생한다. 이말은 추상화를 하게 되면 개발자의 코드를 한 번 더 열어봐야 한다. 이것이 코드 추상화의 단점이다.
  • 그러므로 기능을 확장할 가능성이 없다면 처음에는 구현체로 하는 것도 좋다.

스프링의 핵심원리 이해 - 예제 만들기

프로젝트 생성

  1. start.spring.io에 접속한다
  2. 아래와 같이 설정 후 Genereate 한다.
    • Project : Gradle Project
    • Language: Java
    • Spring Boot : 2.6.7 (정식 릴리즈 된 버전 중 가장 최신 버전)
    • Project Metadata (여긴 Custom하게 자기가 설정하고 싶은 이름으로 설정하면 된다)
      • Group: hello
      • Artifact : core
      • Packaging : Jar
      • Java : 11
    • Dependencies
      • Add Dependencies 클릭
      • Spring Web을 입력 한 후, Spring Web을 클릭해서 추가
      • 그 외 사이드 프로젝트에서 자신이 사용할 의존성 추가

core 프로젝트 살펴보기

📂 hello-spring // 루트 폴더
├─ gradle
│ ├── wrapper // Gralde을 쓰는 폴더
├─── 📂 src // java 소스 상위 폴더  
│ ├── 📂 main 
│ │ ├─── java      // 실제 패키지와 java 소스 파일들이 위치한 곳
│ │ ├─── resources // 실제 자바코드 파일을 제외한 어떤 xml이나 properties, HTML, application 설정파일 등의 리소스    
│ ├── test         // TestCode와 관련된 소스들이 위치한 곳
│ │ ├─── java      
│ │   ├─── hello.hellospring
│ ├── build.gradle // 버전 설정 및 라이브러리 가져오는 역할
├─ JRE System Library // 사용하는 Jaba Library
├─ Project and External Dependencies // Project에서 사용하는 외부 라이브러리
├─ 📂 bin // 빌드 후 실행 파일 등이 생성
├─ 📂 gralde // graldew가 사용하는 실행 파일
├─ build.gradle
├─ graldew // Gradle Wrapper 쉘 스크립트로 로컬에 따로 프로젝트에 선언된 Gradle 버전 설치 필요 없이 
│             자동으로 해당 OS 및 버전과 호환되는 Gradle 실행 파일을 다운받아서 프로젝트를 빌드할 수 있게 하는 일종의 유틸리티 도구
├─ gralde.bat // gradlew의 Windows 용 스크립트
├─ HELP.md // Gradle 도움말
├─ settings.gradle : 프로젝트의 Gradle 설정 파일

Gradlew를 이용한 빌드 및 실행

  • 파일 탐색기를 실행한다 (단축키 : Windows+E)
  • 내 프로젝트 경로로 이동한다. (나의 경우는 C:\Ara\공부\YAPP\20th-Android-Team-2-BE-Personal-dev로 이동)
  • 폴더 경로를 선택 후 cmd를 입력한 뒤 엔터키를 누른다.
  • cmd 창에서 아래 명령을 실행한다.
    gradlew tasks
    

    그럼 아래와 같은 결과가 출력된다. 중요한 것들은 ⭐ 표시한 아래 3가지이다.

Starting a Gradle Daemon (subsequent builds will be faster)

> Task :tasks

------------------------------------------------------------
Tasks runnable from root project 'bestFriend'
------------------------------------------------------------

Application tasks
-----------------
⭐ bootRun - Runs this project as a Spring Boot application.

Build tasks
-----------
assemble - Assembles the outputs of this project.
bootBuildImage - Builds an OCI image of the application using the output of the bootJar task
bootJar - Assembles an executable jar archive containing the main classes and their dependencies.
bootJarMainClassName - Resolves the name of the application's main class for the bootJar task.
bootRunMainClassName - Resolves the name of the application's main class for the bootRun task.
⭐ build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.

Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the main source code.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'bestFriend'.
dependencies - Displays all dependencies declared in root project 'bestFriend'.
dependencyInsight - Displays the insight into a specific dependency in root project 'bestFriend'.
dependencyManagement - Displays the dependency management declared in root project 'bestFriend'.
help - Displays a help message.
javaToolchains - Displays the detected java toolchains.
outgoingVariants - Displays the outgoing variants of root project 'bestFriend'.
projects - Displays the sub-projects of root project 'bestFriend'.
properties - Displays the properties of root project 'bestFriend'.
tasks - Displays the tasks runnable from root project 'bestFriend'.

Verification tasks
------------------
check - Runs all checks.
⭐ test - Runs the test suite.

Rules
-----
Pattern: clean<TaskName>: Cleans the output files of a task.
Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration.

To see all tasks and more detail, run gradlew tasks --all

To see more detail about a task, run gradlew help --task <task>

BUILD SUCCESSFUL in 8s
1 actionable task: 1 executed

그럼 여기서 빌드를 수행해보자

gradlew build

빙드가 성공했으면 ./build/libs 아래에 jar 파일이 생성된다. 나중에 배포할 때 이 jar 파일을 서버에서 실행하면 웹서버까지 같이 띄워지기 떄문에 Tomcat을 따로 설정할 필요도 없으니 얼마나 간편한가!

참고