Template Engine

📌 동적인 웹 페이지를 생성하기 위해 사용되는 도구이며 템플릿을 기반으로 정적인 부분과 동적인 데이터를 결합하여 HTML, XML 등 의 문서를 생성하는 역할을 수행한다.

  •  우리가 흔히 말하는 UI(User Interface)를 만들며, SSR(Server Side Rendering)에 사용된다.
  • 정적 출력은 [안내 메시지]와 같은 모든 사용자에게 동일하게 보이는 데이터를 말하고 동적 데이터는 [사용자 이름]과 같은 사용자, 상황마다 다른 결과를 표현하는 데이터다.
  • 아래와 같이 서버에서 보내는 데이터를 html쪽에서 정의하는 방식으로 접근이 편리하게 만들어준다.
    <!-- 정적 출력 -->
    <h1>안녕하세요, 방문해주셔서 감사합니다!</h1>

    <!-- 동적 출력 -->
    <p>안녕하세요, <span th:text="${name}">이름</span>님!</p>


	[실제 출력]

    <!-- 정적 출력 -->
    <h1>안녕하세요, 방문해주셔서 감사합니다!</h1>

    <!-- 동적 출력 -->
    <p>안녕하세요, <span>철수</span>님!</p>

  • 템플릿 엔진이 나온 이유
    • 자바 코드로 HTML을 만들어 내는 것이 아닌 HTML 문서에 동적으로 변경해야 하는 부분만 자바 코드를 넣을 수 있다면 더 편리하다.
  • 대표적인 템플릿 엔진
    1. Thymeleaf
      • Spring과 통합이 잘 되어있다.
      • 다양한 기능을 포함하고 있다.
    2. JSP(Java Server Pages)
      • 예전엔 많이 사용했으나, 현재 안 쓰는 추세
    3. FreeMarker
    4. Velocity
    5. Mustache

 

MVC 패턴 개요

📌 Servlet이나 JSP만으로 비지니스 로직과 View Rendering 까지 모두 처리하면 너무 많은 역할을 하게 되고 유지보수가 굉장히 어려워져서(책임이 너무 많음) 고안된 패턴이다. Web Application은 일반적으로 MVC(Model View Controller) 패턴을 사용한다.

  • Servlet 문제점
    • 화면을 그리는 View 영역과 비지니스 로직이 Servlet 하나에 모두 섞여있다.
    • 책임을 너무 많이 가지고 있다.

Servlet 동작 순서

  1. 사용자가 Client(브라우저)를 통해 서버에 HTTP Request 즉, API 요청을 한다.
  2. 요청을 받은 Servlet 컨테이너는 HttpServletRequest, HttpServletResponse객체를 생성한다.
  3. 설정된 정보(URL, HTTP Method)를 통해 어떠한 Servlet에 대한 요청인지 찾는다.
  4. 해당 Servlet에서 service 메서드를 호출한 뒤 브라우저의 요청 Method에 따라 doGet() 혹은doPost() 등의 메서드를 호출한다.
  5. 서버에서 응답을 생성한 뒤 HttpServletResponse객체에 응답을 담아 Client(브라우저)에 반환한다.
  6. 응답이 완료되면 생성한 HttpServletRequest, HttpServletResponse객체를 소멸한다.
  • JSP
    • Servlet 코드에서 HTML을 만드는 부분인 View가 분리되었다.
    • 하지만 여전히 문제가 있는데, Servlet만 사용할 경우 View를 위한 코드와 비지니스 로직을 처리하는 코드가 Servlet에 모두 존재하며 유지보수가 어려워진다.
    • JSP를 사용하여 View를 분리하였지만 비지니스 로직의 일부가 JSP파일안에 존재한다. 여전히 책임이 많아 유지보수가 어렵다.

 

MVC 패턴

📌 하나의 Servlet이나 JSP로 처리하던 것들을 Model, View, Controller 영역으로 나눈것이다.

 

  • 핵심 내용
    • View가 분리된 이유의 핵심은 변경이다.
    • 기획이 변하지 않는 이상 비지니스 로직과 View의 수정 원인은 별개로 발생한다.
      • 화면 구성에 수정이 발생하면 View만 변경
      • 요구사항에 수정이 발생하는 경우 비지니스 로직 변경
    • 즉, 서로 연관이 없는 코드끼리 함께 존재할 필요가 없다. 완전히 분리하자

 

MVC 패턴 구조

  • Controller
    • 예시 코드에서 Servlet에 해당하는 영역이다.
    1. HTTP Request를 전달받아 파라미터를 검증한다.
    2. 비지니스 로직을 실행한다.
      • 비지니스 로직을 Controller에 포함하게되면 Controller가 너무 많은 역할을 담당하게 되어 일반적으로 Service Layer를 별도로 만들어서 처리한다.
      • Database와 상호작용 하는 Layer를 따로 구분하여 Repository Layer를 추가로 구성한다.
      • 위와 관련된 자세한 내용인 Layered Architecture는 다음 강의에서 알아보자.
      • Controller도 비지니스 로직을 포함할 수 있지만 일반적으로 Service Layer를 호출하는 역할을 담당한다.
    3. View에 전달할 결과를 조회하여 Model 객체에 임시로 저장한다.
  • Model
    1. View에 출력할 Data를 저장하는 객체이다.
    2. View는 비지니스 로직이나 Data 접근을 몰라도 되고 View Rendering에만 집중하면 된다.(책임 분리)
  • View
    • 예시 코드에서 JSP에 해당하는 영역이다.
    1. Model 객체에 담겨져 있는 Data를 사용하여 화면을 Rendering 한다.

 

 

MVC 패턴의 문제점

📌 MVC 패턴을 적용 후 View의 역할은 필요한 데이터를 Model 에서 참조하여 화면을 그리는 역할만 수행하면 된다. 하지만 Controller에 해당하는 부분은 여전히 문제를 가지고 있다.

  • 문제점
    1. dispatcher.forward(request, response) View로 이동하는 forward( 서버가 하나의 요청을 다른 리소스(예: JSP, 서블릿)로 전달 )가 항상 중복 호출된다.
    2. String path= “/WEB-INF/views/new-form.jsp” View의 path를 입력(중복 작업)한다.
      1. jsp 파일의 경로 혹은 이름이 바뀌면 해당 코드가 변경되어야 한다.
      2. JSP 이외의 확장자를 사용하려면 전체가 변경되어야 한다.
    3. 위의 내용을 설명하면, 클라에서 서버에 계속해서 같은 내용을 반복 요청한다는 것, 그런데 그걸 받는 컨트롤러가 하나에만 배치되면 되는데 다 각각 배치된다는 것.. (카페 알바에게 아메리카노 3잔시켰는데 다른 주문도 많은데 알바 3명이서 1개씩 만들고 있다...)
    4. HttpServletResponse 객체를 사용하는 경우가 적다. (JSP에서 모두 해결하기 때문)
      1. HttpServletRequest와 HttpServletResponse는 Test 코드를 작성하기도 매우 힘들다.
    5. 공통 기능이 추가될수록 Controller에서 처리해야 하는 부분들이 많아진다.
  • 공통 기능 처리
    • 모든 컨트롤러에서 공통으로 적용되는 기능을 뜻한다.
    ex) Log 출력, 인증, 인가 등

  • 공통 기능을 Method로 분리하여 각각의 컨트롤러에서 사용하면 되는것 아닌가요? 라고 생각이 가능하지만, 공통 기능으로 만들어놓은 Method 또한 항상 중복적으로 호출이 필요합니다. 또한, 사람인 개발자가 작업하다보면 Method를 호출하는 일을 깜빡 할수도 있고 Method가 많아지면 많아질수록 Controller의 책임이 점점 커진다. 이를 위해 아래의 대안이 나왔다.

 

프론트 컨트롤러 패턴

📌 Servlet(Controller)이 호출되기 전에 공통 기능을 하나의 Servlet에서 처리해주는 패턴이다. 프론트 컨트롤러(Servlet) 하나에 모든 클라이언트측 요청이 들어온다.

  • 프론트 컨트롤러의 역할
    1. 모든 요청을 하나의 프론트 컨트롤러가 받는다.
    2. 공통 기능을 처리한다.
    3. 요청을 처리할 수 있는 Controller를 찾아서 호출한다.(Controller Mapping)
    4. 프론트 컨트롤러를 제외한 나머지 컨트롤러는 Servlet을 사용하지 않아도 된다.
      • 일반 Controller들은 HttpServlet을 상속( HTTP 프로토콜 기반의 요청과 응답을 처리하기 위해 제공되는 클래스 )받거나, @WebServlet(URL 매핑 여기서는 url과 servlet과의 매핑을 말한다.)을 사용하지 않아도 된다. = 프론트 컨트롤러가 처리함
      • @WebServlet의 설명이 이해가 안될 수도 있는데, url은 말 그대로 주소 식별자다. url에 보내는 요청을 보고 어느 servlet에서 처리를 할지 정하고 해당 servlet에 배치(매핑)하는 과정은 완전별개의 문제이다.
  • 프론트 컨트롤러 의문점
    • 프론트 컨트롤러를 사용하면 모든 컨트롤러에서 같은 형태의 응답을 해야하는가?

    • 위 그림처럼 공통 처리 로직에 모든 컨트롤러가 연결되기 위해서는 모든 컨트롤러가 return 하는 결과의 형태가 동일해야 한다.
    • 하지만, Controller 마다 로직이나 응답해야하는 결과는 당연히 다를테고 응답을 동일하게 맞추려고 한다면 해당 애플리케이션은 확장성, 유지보수성을 잃는다.
    • 공통 로직에서 응답별로 퍼즐을 다시 하나하나 처리할 수 있으나 공통 부분의 책임이 너무 커지게된다. 또한, 컨트롤러에서 반환되는 결과가 달라지면 공통처리 부분의 변경또한 불가피하다.
    • 그래서 아래의 대안이 또 나왔다.

 

어댑터 패턴

📌 어댑터 패턴호환되지 않는 인터페이스를 가진 클래스들이 함께 동작할 수 있도록 중간에서 변환을 도와주는 디자인 패턴입니다. 주로, 이미 존재하는 코드와 새로운 코드 사이의 호환성 문제를 해결하는 데 사용됩니다.

  • 컨트롤러들은 동일한 인터페이스를 구현하도록 하고 해당 인터페이스와 공통 로직 사이에 어댑터를 두어 유연하게 만든다. 서로 다른 인터페이스를 갖는 두 클래스를 연결해주는 패턴이다.
  • 이해가 잘 안된다면 아래의 접은글을 참고
더보기
 

비유: 요리사, 레시피, 그리고 주방

  1. 요리사(각 컨트롤러)
    • 요리사는 특정 요리(기능)를 책임지고 있습니다.
    • 하지만 요리사는 "자신만의 레시피(방법)"만 알고, 다른 요리사들이 어떤 레시피를 사용하는지 알지 못할 수도 있습니다.
  2. 레시피(인터페이스 또는 요청 처리)
    • 레시피는 요리사가 따라야 하는 공통된 규칙(인터페이스)입니다.
    • 예를 들어, 레시피에 따라 어떤 순서로 재료를 조합할지 정의됩니다.
    • 모든 요리사가 이 레시피를 준수해야 주방이 동작을 관리하기 쉬워집니다.
  3. 주방(공통 로직 또는 프론트 컨트롤러)
    • 주방은 요리사들이 만든 다양한 레시피를 필요한 시점에 호출하고, 손님(사용자)에게 제공할 음식을 최종적으로 내놓습니다.
    • 주방은 모든 요리를 직접 만들지 않고, 요리사들에게 필요한 일을 분담시킵니다.

어댑터 패턴의 맥락에서

주방에서 레시피(인터페이스)를 공유하여 요리사들이 협업 가능하게 만드는 구조는 어댑터 패턴과 비슷합니다.

  • 주방(공통 처리 로직)과 요리사(개별 로직)가 서로 다른 방식을 사용하고 있다면, 어댑터(중간 번역기)를 통해 조율할 수 있습니다.

프론트 컨트롤러를 요리사와 주방에 비유하면

  1. 요리사 (각 컨트롤러)
    • @Controller를 사용한 일반 컨트롤러.
    • HTTP 요청이 들어오면, 요리사(컨트롤러)는 특정 요리를 준비하는 역할을 수행.
    • 클라이언트 요청에 따라 요리사는 자신의 비즈니스 로직에만 집중.
  2. 레시피 (인터페이스)
    • 요리사가 동작할 때 따라야 하는 규칙.
    • 예를 들어, Spring MVC에서는 @RequestMapping 또는 @GetMapping 등의 애너테이션이 이런 역할을 함.
  3. 주방 (프론트 컨트롤러)
    • Spring의 DispatcherServlet 같은 역할.
    • 모든 요청이 주방으로 들어오고, 주방은 적절한 요리사(컨트롤러)를 찾아 요청을 전달.
    • 공통적인 작업(예: 인증, 로깅, 예외 처리 등)을 주방에서 처리한 뒤 요리사에게 넘김.

  1. 컨트롤러(Handler)는 비지니스 로직을 처리하고 알맞은 결과를 반환한다.
  2. 어댑터는 공통 로직과 컨트롤러(Handler)가 자연스럽게 연결되도록 한다.
  3. 프론트 컨트롤러는 공통으로 처리되는 로직을 수행한다.
  • 어댑터 패턴 장점
    • 프론트 컨트롤러, 어댑터, 핸들러 모두 각자의 역할만 수행한다. (책임 분리)
    • 새로운 컨트롤러(Handler)가 추가되어도 컨트롤러와 어댑터만 추가한다면 공통 로직의 변경이 발생하지 않는다.

 

요약

  1. Servlet 사용
    • 비지니스 로직을 처리하는 코드와 화면을 그리는 View 코드가 함께 존재하는 문제
  2. JSP 사용
    • View 에 해당하는 코드를 분리하였지만, 여전히 비지니스 로직을 JSP에 포함하는 문제
  3. MVC 패턴 사용
    • 공통 로직을 처리하는것에 코드가 중복되는 문제
  4. 프론트 컨트롤러 패턴 사용
    • 공통 로직을 하나의 입구에서 처리하기 위해서 프론트 컨트롤러 패턴 적용
    • 각각의 핸들러 호출 후 응답을 프론트 컨트롤러에 맞게 변형시켜야 하는 문제
  5. Spring MVC 사용
    • 프론트 컨트롤러 패턴, 어댑터 패턴이 모두 적용된 현재
    • 우리가 사용하는 Spring을 이용한 Web Application 개발 방식에 사용됨
    • Spring은 MVC 패턴에 프론트 컨트롤러 패턴, 어댑터 패턴이 적용되어 있다.

 

'CS ( Computer Science ) > 네트워크 (Networking)' 카테고리의 다른 글

[Net] Token & JWT  (0) 2024.12.29
[Net] Cookie  (1) 2024.12.28
[Net] API 설계  (0) 2024.12.12
[Net] Rendering  (1) 2024.12.05
[Net] Servlet  (1) 2024.12.04

📚 Java 웹 기술은 서블릿과 JSP로 시작해 MVC 패턴을 거쳐 MVC 프레임워크의 도입으로 구조화되었으며, 이후 어노테이션 기반의 Spring MVC와 비동기 처리에 최적화된 Spring WebFlux로 발전해왔다.

 

  1. Servlet의 등장 (1997)
    • 개요
      • Java를 사용한 웹 개발의 시초로, 서버에서 동적으로 콘텐츠를 생성하기 위해 사용되었다. 클라이언트의 요청을 받고, 그에 대한 응답을 생성하는 기본적인 구조를 제공했다.
    • 단점
      • 코드의 복잡성이 증가하고 유지보수가 어려워졌다.
  2. JSP (JavaServer Pages) 도입 (1999)
    • 개요
      • JSP는 HTML 내에 Java 코드를 삽입할 수 있는 기술로, 웹 페이지를 더 쉽게 동적으로 생성할 수 있도록 도와주었다.
    • 단점
      • JSP 내에 비즈니스 로직을 분리할 수 없다.
  3. Servlet, JSP 기반의 MVC 패턴 도입
    • 개요
      • MVC 패턴이 도입되면서, UI, 비지니스 로직, 데이터를 분리하여 개발하는 방식이 등장했다.
      • Servlet은 주로 컨트롤러(비지니스 로직)로 사용되었고, JSP는 뷰(UI)를 담당하게 되었다.
    • 장점
      • MVC 패턴은 웹 애플리케이션의 유지보수성과 확장성을 크게 향상시켰다.
    • 단점
      • 개발자가 중복적으로 설정해줘야 하는 부분들이 다수 발생했다.
  4. MVC 프레임워크의 등장과 발전 (2000~2010)
    • 개요
      • Struts, Spring 등의 MVC 프레임워크가 등장하며, 웹 애플리케이션 개발이 더욱 구조화되고 효율적으로 변했다.
      • 그중 Spring MVC는 단순하면서도 강력한 기능을 제공하여, Java 웹 개발의 표준으로 자리 잡게 되었다.
      • 중복적으로 설정해야 하는 부분들을 프레임워크로 자동화 했다.
    • 단점
      • 여전히 애플리케이션 개발 관련 설정이 복잡했다.
  5. Annotation 기반의 Spring MVC(2007~현재)
    • 개요
      • Annotation을 통해 애플리케이션 설정의 복잡함을 줄여주었다.
    • 장점
      • 더 직관적이고 간결한 방식으로 웹 애플리케이션을 개발할 수 있게 되었다.
  6. Spring Boot의 등장(2014~현재)
    • 개요
      • Spring 프레임워크를 보다 쉽게 사용하도록 만든 도구로, 설정과 복잡한 초기 설정 작업을 자동화했다.
      • 내장 Tomcat을 가지고 있다.
    • 장점
      • 개발자들이 빠르게 애플리케이션을 개발할 수 있도록 도와준다.
  • 최신 기술 동향
    1. Web Servlet
      • Spring MVC
        • 안정적이고 동기식 프로그래밍 모델을 기반으로 한 웹 애플리케이션 개발에 널리 사용된다.
    2. Web Reactive
      • Spring WebFlux
        • 비동기 및 넌블로킹 모델을 기반으로 한 웹 프레임워크로, 높은 동시성을 요구하는 애플리케이션에서 효율적인 성능을 제공한다. 함수형 프로그래밍 스타일을 지원하며, 서블릿 기술 대신 Netty 등의 비동기 서버를 사용한다.
        • 서블릿 기술을 사용하지 않으며, 실시간 데이터를 처리하거나 높은 동시성을 요구하는 애플리케이션에 적합하다.
        • RDBMS 지원 부족과 높은 기술적 난이도 등으로 인해, 아직은 MVC 모델이 많은 실무에서 더 많이 사용되고 있다.

'Back-End (Web) > JAVA' 카테고리의 다른 글

[JAVA] 열거형 ( Enum )  (0) 2024.11.22
[JAVA] HASH란 무엇인가  (1) 2024.11.21
[JAVA] 자바의 정렬  (0) 2024.11.21
[JAVA] 응용 정리  (1) 2024.11.15
[JAVA] NULL  (0) 2024.11.13
요구사항 API URI 설계
  회원 목록 조회
• 회원 조회
• 회원 등록
• 회원 수정
• 회원 삭제
  회원 목록 조회 /read-member-list
• 회원 조회 /read-member-by-id
• 회원 등록 /create-member
• 회원 수정 /update-member
• 회원 삭제 /delete-member

 

단적으로 위의 설계는 틀린 방식이다.

가장 중요한 것은 리소스 식별

 

API URI 고민

URI(Uniform Resource Identier)

 

• 리소스의 의미는 뭘까?

  • 회원을 등록하고 수정하고 조회하는게 리소스가 아니다!
  • 예) 미네랄을 캐라 -> 미네랄이 리소스
  • 회원이라는 개념 자체가 바로 리소스다.

• 리소스를 어떻게 식별하는게 좋을까?

  • 회원을 등록하고 수정하고 조회하는 것을 모두 배제
  • 회원이라는 리소스만 식별하면 된다. -> 회원 리소스를 URI에 매핑
요구사항 API URI 설계
  회원 목록 조회
• 회원 조회
• 회원 등록
• 회원 수정
• 회원 삭제
  회원 목록 조회 /members
• 회원 조회 /members/{id}
• 회원 등록 /members/{id}
• 회원 수정 /members/{id}
• 회원 삭제 /members/{id} 

참고: 계층 구조상 상위를 컬렉션으로 보고 복수단어 사용 권장(member -> members)

 

 

리소스와 행위 분리

가장 중요한 것은 리소스를 식별하는 것

 

• URI는 리소스만 식별!

• 리소스와 해당 리소스를 대상으로 하는 행위을 분리

  • 리소스: 회원
  • 행위: 조회, 등록, 삭제, 변경

• 리소스는 명사, 행위는 동사 (미네랄을 캐라)

• 행위(메서드)는 어떻게 구분?

'CS ( Computer Science ) > 네트워크 (Networking)' 카테고리의 다른 글

[Net] Cookie  (1) 2024.12.28
[Net] MVC 패턴  (0) 2024.12.13
[Net] Rendering  (1) 2024.12.05
[Net] Servlet  (1) 2024.12.04
[Net] Web Application  (1) 2024.12.03

Gradle

📌 Java와 유사한 문법 구조를 가진 Groovy기반의 스크립트 언어를 사용하며 다양한 소프트웨어를 빌드(Build)할 수 있는 유연한 빌드 자동화 도구이다.

  • 빌드란 소스 코드를 컴퓨터가 실행 가능한 파일로 변환해주는 작업이다.

빌드와 빌드 자동화 도구

 

 

[1]  Gradle 특징

  1. 유연성
    • 복잡한 빌드 시나리오를 처리할 수 있는 유연한 시스템을 제공한다.
    • 빌드 스크립트를 통해 다양한 빌드 작업을 정의하고, 필요한 경우 커스터마이징할 수 있다.
  2. 성능
    • Build Cache
      • 빌드 결과물을 캐싱하여 재사용한다.
      • 라이브러리 의존성을 캐싱하여 재사용한다.
    • 점진적 빌드
      • 마지막 빌드 호출 이후 변경된 부분만 빌드한다.
      • 변경되지 않은 부분은 캐시 결과를 검색해 재사용한다.
    • 데몬 프로세스
      • 다음 빌드 작업을 위해 백그라운드에서 대기하는 프로세스
      • 초기 빌드 이후부터는 빌드 실행 시 초기화 작업을 거치지 않는다.
  3. 멀티 프로젝트 빌드 지원
    • 공통으로 사용하는 클래스를 모듈로 만들어 독립적인 각 프로젝트에서 사용할 수 있도록 한다.
  4. 설정 주입 방식
    • 필요한 설정을 직접 프로젝트에 주입하는 방식이다.
    • 공통되는 정보는 묶어서 한번에 주입이 가능하다.
    • 프로젝트별로 설정을 다르게 주입할 수 있다.

 

[2]  Gradle과 다른 빌드 도구 비교

  Gradle Maven Ant
설정 방식 Groovy/Kotlin DSL XML XML
성능 빠름 (증분 빌드, 캐싱 지원) 비교적 느림 느림 (모든 작업을 매번 실행)
의존성 관리 지원 (Maven, Ivy 호환) 지원 (Maven Central) 제한적 (Ivy 필요)
확장성 매우 유연, 사용자 정의 플러그인 가능 제한적 매우 유연
주요 사용 사례 현대적 프로젝트, 멀티 플랫폼 지원 Java 프로젝트 중심 간단한 자동화 작업

 

[3]  주요 Gradle 명령어

gradle tasks 사용 가능한 모든 작업(task) 목록을 표시.
gradle build 프로젝트를 빌드(컴파일, 테스트, 패키징 등)합니다.
gradle clean 이전 빌드 결과를 삭제합니다.
gradle test 프로젝트의 테스트를 실행합니다.
gradle run 애플리케이션 실행 (특정 플러그인이 필요).
gradle dependencies 프로젝트의 의존성을 확인합니다.
gradle assemble 소스 코드를 컴파일하고 JAR 파일 또는 기타 배포 가능한 아티팩트를 생성합니다.

 

 

[4]  Gradle에서 의존성 관리

Gradle은 의존성 관리를 통해 프로젝트의 외부 라이브러리를 다운로드하고 관리합니다.
의존성은 dependencies 블록 안에서 정의하며, 저장소는 repositories 블록에서 설정합니다.

1. 의존성 추가

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web' // 실행 시 필요한 라이브러리
    testImplementation 'org.junit.jupiter:junit-jupiter' // 테스트 용도 라이브러리
}

2. 저장소 설정

repositories {
    mavenCentral() // Maven Central Repository
    jcenter() // JCenter Repository
}

 

[5]  Gradle 멀티 프로젝트 설정

Gradle은 대규모 애플리케이션에서 멀티 프로젝트 빌드를 지원합니다.

  • 루트 프로젝트의 settings.gradle
rootProject.name = 'my-multi-project'
include 'module1', 'module2' // 하위 프로젝트 포함
  • 각 하위 프로젝트의 build.gradle
plugins {
    id 'java'
}

dependencies {
    implementation project(':module1') // 다른 모듈을 의존성으로 추가
}

 

 

[6]  Gradle의 장점

  1. 효율적인 빌드 속도:
    • Gradle의 증분 빌드 기능은 수정된 부분만 빌드하여 속도를 대폭 향상시킵니다.
  2. 유연한 설정:
    • Groovy 또는 Kotlin DSL을 사용하여 빌드 스크립트를 자유롭게 작성 가능하며, 복잡한 프로젝트도 쉽게 관리 가능합니다.
  3. 강력한 플러그인 시스템:
    • 수많은 플러그인이 제공되며, 사용자 정의 플러그인을 만들어 프로젝트에 통합할 수 있습니다.
  4. 확장성:
    • 다양한 언어 및 플랫폼 지원으로 다양한 종류의 애플리케이션 빌드 가능.
  5. 커뮤니티 및 문서화:
    • 활발한 커뮤니티와 풍부한 공식 문서로 개발자가 쉽게 문제를 해결할 수 있습니다.

 

[7]  Gradle의 주요 단점

초기 학습 곡선 Gradle은 유연성과 DSL(Groovy/Kotlin) 기반의 스크립트를 제공하지만, 설정이 복잡할 경우 학습 곡선이 가파릅니다.
느린 초기 빌드 증분 빌드나 빌드 캐시를 사용하지 않는 초기 빌드는 Maven보다 느리게 느껴질 수 있습니다.
모호한 에러 메시지 복잡한 빌드 스크립트에서 문제가 발생할 경우, 에러 메시지가 직관적이지 않거나 원인을 파악하기 어려운 경우가 있습니다.
DSL 의존성 Groovy 또는 Kotlin DSL을 학습해야 하며, 기존 XML 기반(Maven 등)에 익숙한 사용자에게는 진입 장벽이 될 수 있습니다.
플러그인 의존성 문제 플러그인의 호환성 문제가 발생하거나 최신 플러그인을 찾기 어려운 경우가 종종 있습니다.
의존성 관리 중복 가능성 프로젝트 구조가 복잡해질수록 의존성 충돌 문제가 발생할 가능성이 있으며, 이를 해결하기 위해 추가적인 설정이 필요합니다.
멀티 프로젝트 빌드 관리 어려움 멀티 프로젝트 설정이 유연한 대신 복잡한 빌드 환경에서는 디버깅 및 설정 관리가 까다로울 수 있습니다.
Maven보다 적은 레퍼런스 Maven에 비해 더 적은 사용자와 레퍼런스를 제공하므로, 특정 문제에 대한 해결 정보를 찾기 어려울 때가 있습니다.

 

 

build.gradle

📌 Groovy 기반 언어의 빌드 스크립트로 스크립트를 작성하면 소스 코드를 빌드하고, 라이브러리들의 의존성을 관리할 수 있다.

  • Gradle 프로젝트의 빌드 설정의존성 관리를 담당하는 핵심 구성 파일입니다. Groovy DSL을 사용하여 작성되며, Gradle 프로젝트의 다양한 동작을 정의
// 1. 플러그인 정의
plugins {
    id 'java' // Java 플러그인
    id 'org.springframework.boot' version '3.0.0' // Spring Boot 플러그인
}

// 2. 프로젝트 정보
group = 'com.example' // 그룹 ID
version = '1.0.0' // 버전 정보
sourceCompatibility = '17' // Java 버전

// 3. 저장소 설정
repositories {
    mavenCentral() // Maven Central 저장소
}

// 4. 의존성 관리
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web' // Spring Boot Starter Web
    testImplementation 'org.springframework.boot:spring-boot-starter-test' // 테스트 의존성
}

// 5. 태스크 정의 (선택적)
tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8' // UTF-8 인코딩 설정
}

[1]  주요 구성 요소

1. 플러그인

  • Gradle은 플러그인을 통해 프로젝트의 기능을 확장합니다.
plugins {
    id 'java' // Java 애플리케이션 빌드용
    id 'org.springframework.boot' version '3.0.0' // Spring Boot 프로젝트 설정용
    id 'io.spring.dependency-management' version '1.1.0' // Spring 의존성 관리
}

 

2. 프로젝트 정보

  • 프로젝트의 기본 정보와 Java 버전 등을 설정합니다.
group = 'com.example' // 조직명
version = '1.0.0' // 애플리케이션 버전
sourceCompatibility = '17' // Java 버전

 

3. 저장소

  • 외부 라이브러리를 가져오는 저장소를 지정합니다.
repositories {
    mavenCentral() // Maven Central Repository
    jcenter() // JCenter Repository (필요 시 추가)
}

 

4. 의존성

  • 프로젝트에서 사용하는 라이브러리와 의존성을 관리합니다.
  • 종류:
    • implementation: 애플리케이션 실행 시 사용.
    • testImplementation: 테스트 전용.
    • runtimeOnly: 런타임 전용.
    • compileOnly: 컴파일 시에만 필요.
dependencies {
    // JPA
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    // SECURITY
    implementation 'org.springframework.boot:spring-boot-starter-security'

    // WEB
    implementation 'org.springframework.boot:spring-boot-starter-web'

    // LOMBOK
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    // MySQL
    runtimeOnly 'com.mysql:mysql-connector-j'

    // Validation
    implementation 'org.springframework.boot:spring-boot-starter-validation'

    // JWT
    compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
    runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
    runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'

    // DEV_TOOL
    developmentOnly 'org.springframework.boot:spring-boot-devtools'

    // TEST
    testRuntimeOnly 'com.h2database:h2'
    testCompileOnly 'org.projectlombok:lombok'
    testCompileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
}
  • 의존성 설정
    • 라이브러리를 추가하는 시점을 설정할 수 있다.
    • Implementation
      • 컴파일, 런타임 시점 모두에서 사용한다.
    • compileOnly
      • 컴파일할 때만 사용되고 런타임 때에는 사용하지 않는다.
    • runtimeOnly
      • 런타임 때만 사용한다.
    • testImplementation
      • 테스트할 때만 사용한다.

 

5. 태스크

  • Gradle 태스크를 추가하거나 수정할 수 있습니다.
tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8' // UTF-8 인코딩 설정
}

task printHello {
    doLast {
        println 'Hello, Gradle!' // 사용자 정의 태스크
    }
}

 

 

 

Task

📌 빌드 과정에서 실행 가능한 작업 단위입니다. 예를 들어, 컴파일, 테스트, 패키징, 정리 등을 수행하는 작업입니다. Gradle에서는 기본 제공 태스크사용자 정의 태스크를 모두 사용할 수 있습니다.

[1]  태스크의 기본 구조

task <태스크 이름> {
    // 태스크 정의
    doLast {
        println '이 태스크는 실행 시 마지막에 실행됩니다.'
    }
}
  • doLast: 태스크의 마지막 작업을 정의합니다.
  • doFirst: 태스크의 첫 번째 작업을 정의합니다.

[2]  기본 제공 태스크

Gradle은 프로젝트의 전형적인 빌드 작업을 처리하기 위해 여러 기본 태스크를 제공합니다.

태스크 이름설명

build 전체 빌드 프로세스를 실행합니다.
clean 이전 빌드에서 생성된 파일(예: build 폴더)을 삭제합니다.
test 프로젝트의 테스트 코드를 실행합니다.
assemble 프로젝트를 컴파일하고 패키징합니다.
dependencies 프로젝트의 의존성 트리를 출력합니다.
tasks 프로젝트에서 사용 가능한 모든 태스크를 나열합니다.
bootRun (Spring Boot) Spring Boot 애플리케이션을 실행합니다.

[3]  사용자 정의 태스크

Gradle에서 사용자가 직접 태스크를 정의할 수 있습니다.

(1) 간단한 사용자 정의 태스크

task hello {
    doLast {
        println 'Hello, Gradle!'
    }
}

 

(2) 실행 순서 지정 (dependsOn)

task taskA {
    doLast {
        println 'Executing Task A'
    }
}

task taskB(dependsOn: taskA) {
    doLast {
        println 'Executing Task B'
    }
}
  • dependsOn: 지정된 태스크가 먼저 실행된 후 현재 태스크가 실행됩니다.
  • 실행 순서:
    1. taskA
    2. taskB

 

(3) 조건부 실행 (onlyIf)

task conditionalTask {
    doLast {
        println 'This task will run only if the condition is true.'
    }
    onlyIf {
        System.getProperty('runTask') == 'true'
    }
}
  • Gradle 실행 시 -DrunTask=true 옵션을 통해 태스크 실행 가능:
    bash
    코드 복사
    gradle conditionalTask -DrunTask=true
     

(4) 매개변수 전달 (project.property)

task printMessage {
    doLast {
        println "Message: ${project.property('message')}"
    }
}
  • Gradle 실행 시 매개변수를 전달:
gradle printMessage -Pmessage="Hello, Gradle!"

[4]  태스크 타입 활용

Gradle은 미리 정의된 태스크 타입을 제공합니다. 이들을 사용하면 더 복잡한 작업을 쉽게 설정할 수 있습니다.

(1) Copy 태스크: 파일 복사

task copyFiles(type: Copy) {
    from 'src/resources' // 복사할 디렉터리
    into 'build/output'  // 복사 대상 디렉터리
}

(2) Delete 태스크: 파일 삭제

task cleanTempFiles(type: Delete) {
    delete 'temp' // 삭제할 파일 또는 디렉터리
}

(3) Exec 태스크: 외부 명령 실행

task runScript(type: Exec) {
    commandLine 'sh', './script.sh' // 실행할 명령
}

[5]  다중 프로젝트 빌드에서의 태스크

Gradle은 **다중 프로젝트 빌드(Multi-Project Build)**를 지원합니다. 각 하위 프로젝트에서 정의된 태스크를 중앙 프로젝트에서 호출할 수 있습니다.

예시: 중앙 프로젝트에서 하위 프로젝트 태스크 실행

task buildAll {
    dependsOn ':subproject1:build', ':subproject2:build'
    doLast {
        println 'All subprojects are built!'
    }
}

[6]  Spring Boot 관련 태스크

Spring Boot 플러그인을 사용하면 추가적인 태스크가 제공됩니다.

bootRun Spring Boot 애플리케이션을 실행합니다.
bootJar 실행 가능한 JAR 파일을 생성합니다.
bootBuildImage Docker 컨테이너 이미지 생성(Spring Boot 2.3+).
gradle bootRun
gradle bootJar
gradle bootBuildImage​

 


[7]  Gradle 태스크 명령어

Gradle에서 태스크를 실행하거나 관리할 때 사용하는 주요 명령어:

명령어설명

gradle tasks 프로젝트에서 사용 가능한 모든 태스크를 나열합니다.
gradle <taskName> 특정 태스크를 실행합니다.
gradle <taskName> --dry-run 태스크 실행 시 어떤 작업이 수행될지 시뮬레이션만 보여줍니다.
gradle clean 이전 빌드 결과를 삭제합니다.
gradle build 전체 빌드 작업을 수행합니다.

 

 

Spring Boot

📌 Spring Framework를 기반으로 하여 간편하고 신속하게 애플리케이션을 개발할 수 있도록 도와주는 도구이다.

Web Application 이라는 라면을 끓일 때(만들 때) 조리 도구 세트를 사용한다. 라면 : Java 냄비 : Spring 조리 도구 세트 : Spring Boot

 

 

[1]  Spring Boot의 주요 특징

특징설명

자동 설정 (Auto Configuration) Spring Boot는 애플리케이션의 환경을 자동으로 감지하여 적절한 설정을 자동으로 적용합니다. 이를 통해 많은 설정을 수동으로 할 필요 없이, 개발자가 설정에 대해 걱정하지 않고 애플리케이션을 시작할 수 있습니다.
독립 실행형 애플리케이션 Spring Boot는 내장된 서버(예: Tomcat, Jetty, Undertow 등)를 포함하여, WAR 파일 없이도 실행할 수 있는 독립 실행형 애플리케이션을 만듭니다.
스타터 프로젝트 (Starter Projects) Spring Boot는 필요한 라이브러리나 종속성을 손쉽게 추가할 수 있도록 "스타터" 의존성(예: spring-boot-starter-web, spring-boot-starter-data-jpa 등)을 제공합니다.
프로덕션 준비 Spring Boot는 메트릭스, 상태 점검, 로깅과 같은 프로덕션 환경에서 필요한 다양한 기능을 기본적으로 제공합니다. Actuator 라이브러리를 통해 애플리케이션의 상태를 모니터링하고 관리할 수 있습니다.
Spring Initializr Spring Boot는 프로젝트 초기화를 쉽게 할 수 있도록 https://start.spring.io/에서 다양한 설정을 선택하여 빠르게 프로젝트를 생성할 수 있습니다.
간단한 설정 Spring Boot는 application.properties 또는 application.yml 파일을 사용해 간단하게 애플리케이션의 설정을 관리할 수 있습니다.
서버의 자동 실행 내장 웹 서버를 통해 별도의 설정 없이, 애플리케이션을 java -jar 명령어로 실행할 수 있습니다.
개발자 친화적인 환경 Spring Boot는 애플리케이션의 개발과 디버깅을 빠르게 할 수 있도록 DevTools라는 기능을 제공하며, 코드 변경 시 자동으로 애플리케이션을 재시작하여 개발 편의성을 제공합니다.

 

[2]  Spring Boot의 장점

  1. 빠른 시작:
    • Spring Boot는 자동 설정 및 기본적인 템플릿을 제공하여, 애플리케이션을 몇 가지 설정만으로 빠르게 시작할 수 있습니다. @SpringBootApplication 어노테이션을 추가하면, 기본적인 설정이 자동으로 이루어지고, 애플리케이션을 실행할 수 있는 상태가 됩니다.
  2. 설정 최소화:
    • Spring Boot는 많은 설정을 자동으로 처리하므로 개발자는 비즈니스 로직에만 집중할 수 있습니다. 예를 들어, 데이터베이스 설정, 서버 설정 등 대부분의 설정을 자동으로 처리하여 개발자가 별도로 신경 쓸 필요가 없습니다.
  3. 내장 서버:
    • 내장 서버를 제공하여 별도의 외부 웹 서버(Tomcat, Jetty 등)를 설치할 필요 없이 바로 실행할 수 있습니다. 이는 애플리케이션의 배포를 간소화하고, 실행 파일 하나로 애플리케이션을 배포할 수 있게 만듭니다.
  4. 생산성 향상:
    • Spring Boot는 개발자가 빠르게 애플리케이션을 작성하고 실행할 수 있도록 돕기 위해 많은 스타터 의존성을 제공합니다. 예를 들어, 웹 애플리케이션을 만들기 위한 spring-boot-starter-web, 데이터베이스 연동을 위한 spring-boot-starter-data-jpa 등이 있습니다.
  5. DevTools:
    • Spring Boot는 개발 중에 자동 재시작Hot swapping을 지원하여, 개발자가 코드 수정 후 애플리케이션을 다시 시작하지 않고도 변경 사항을 바로 반영할 수 있도록 돕습니다.

 

[3]  Spring Boot 애플리케이션 구조

  1. 애플리케이션 클래스
    • @SpringBootApplication 어노테이션이 붙은 클래스는 Spring Boot 애플리케이션의 진입점입니다. 이 클래스는 자동으로 필요한 설정을 수행하고 애플리케이션을 실행합니다.
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 자동 설정:
    • Spring Boot는 애플리케이션을 실행할 때, 주어진 환경에 맞춰 자동으로 설정을 적용합니다. 예를 들어, 데이터베이스가 설정되면 자동으로 DataSource와 관련된 설정이 적용됩니다.
  2. 프로퍼티 파일:
    • Spring Boot는 application.properties 또는 application.yml 파일을 통해 애플리케이션의 설정을 관리할 수 있습니다. 데이터베이스 연결 정보, 서버 포트, 로깅 수준 등 다양한 설정을 이 파일에서 처리합니다.
  3. 내장 서버:
    • Spring Boot는 기본적으로 내장된 Tomcat 서버를 포함하고 있으며, 설정에 따라 다른 내장 서버(Undertow, Jetty 등)를 사용할 수 있습니다.

 

[4]  Spring Boot 애플리케이션 실행

  1. Maven 또는 Gradle을 사용한 빌드 후 실행:
    • 애플리케이션을 빌드하고 실행하려면, mvn spring-boot:run 또는 gradle bootRun 명령을 사용할 수 있습니다.
  2. JAR 파일로 실행:
    • Spring Boot 애플리케이션을 JAR 파일로 빌드한 후, java -jar 명령어로 실행할 수 있습니다.
java -jar myapp.jar

 

[5]  Spring Boot 사용 예시

  • 간단한 RESTful 웹 서비스:
@RestController
@RequestMapping("/api")
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello, World!";
    }
}
  • 자동 설정 예시: Spring Boot는 데이터베이스 연결을 자동으로 처리할 수 있습니다. 예를 들어, 데이터베이스 설정을 application.properties에서 지정하면, Spring Boot는 이를 자동으로 인식하고 설정을 완료합니다.
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=secret

 

 

'Back-End (Web) > Spring' 카테고리의 다른 글

[Spring] Spring Annotation  (1) 2024.12.15
[Spring] Spring MVC 패턴  (1) 2024.12.14
[Spring] Spring Framework  (2) 2024.12.09
[Spring] 웹 개발의 흐름  (1) 2024.12.05
[Spring] Spring 웹 애플리케이션 계층 구조  (2) 2024.11.13

Spring Framework

📌 Java Application Framework로 엔터프라이즈 애플리케이션 개발에 주로 사용된다.

  • 엔터프라이즈 애플리케이션은 대규모로 복잡한 비즈니스 프로세스와 데이터를 처리하는 애플리케이션을 뜻한다.
  • IoC (Inversion of Control), AOP (Aspect-Oriented Programming), DI (Dependency Injection) 등의 개념을 바탕으로 개발되었으며, 이를 통해 애플리케이션의 구조를 유연하고 모듈화하여 유지보수성을 높이고, 복잡한 엔터프라이즈 애플리케이션 개발을 더 간단하게 만들어 줍니다.

Spring Framework로 만드는 Web Application 라면 : Java 냄비 : Spring

[1]  Spring Framework 특징

애플리케이션의 다양한 구성 요소를 유연하게 연결하고 관리할 수 있도록 해준다.

  • Spring Framework는 누구나 사용할 수 있는 오픈소스 이다.
  • 모듈화되어 있어 필요에 따라 특정 기능만 선택적으로 사용할 수 있다.
  • Java언어의 가장 큰 특징인 객체 지향 언어의 특징을 살려낸 프레임워크이다.
    • 캡슐화
    • 상속
    • 추상화
    • 다형성

 

[2]  Spring의 구조

Spring Framework는 여러 모듈로 구성되어 있으며, 각 모듈은 특정 목적에 맞는 기능을 제공합니다. 주요 모듈은 다음과 같습니다:

  1. Core Container:
    • Core: Spring의 핵심 기능인 IoC (Inversion of Control) 및 DI (Dependency Injection)를 제공.
    • Beans: 객체 생성 및 관리.
    • Context: Spring의 애플리케이션 컨텍스트 기능을 제공, 이벤트 관리와 같은 다양한 기능 포함.
    • Spring AOP: AOP 기능을 제공하여, 메서드 호출 전후의 동작을 정의.
  2. Data Access/Integration:
    • JDBC: 데이터베이스와의 상호작용을 쉽게 할 수 있도록 지원.
    • ORM: Hibernate, JPA 등 객체-관계 매핑을 처리.
    • JMS: Java 메시지 서비스 (JMS)를 지원하여 메시지 기반 애플리케이션 구현.
  3. Web:
    • Spring MVC: 웹 애플리케이션을 위한 모델-뷰-컨트롤러 패턴을 지원.
    • WebSocket: 실시간 통신을 위한 WebSocket 지원.
    • WebFlux: 반응형 프로그래밍 모델을 기반으로 한 웹 프레임워크.
  4. Security:
    • 인증, 권한 부여 및 다양한 보안 관련 기능을 제공합니다.
  5. Testing:
    • Spring Test 모듈은 Spring 애플리케이션을 테스트하는 데 필요한 다양한 유틸리티를 제공합니다. @SpringBootTest 등을 통해 통합 테스트를 쉽게 할 수 있습니다.

 

[3]  Spring Framework의 주요 특징

1. IoC (Inversion of Control) / DI (Dependency Injection)

  • Spring은 객체를 직접 생성하는 대신, IoC 컨테이너를 통해 객체를 관리합니다. 객체 간의 의존 관계를 DI 방식으로 해결하여, 애플리케이션의 결합도를 낮추고 유연성을 높입니다.
  • 예를 들어, 하나의 클래스가 다른 클래스에 의존할 때, 이를 자동으로 주입해주는 방식입니다.
@Component
public class Service {
    private final Repository repository;

    @Autowired  // Repository를 자동으로 주입
    public Service(Repository repository) {
        this.repository = repository;
    }
}

 

2. AOP (Aspect-Oriented Programming)

  • AOP는 관점 지향 프로그래밍으로, 핵심 비즈니스 로직과 공통 기능(로깅, 트랜잭션 관리 등)을 분리하여 개발할 수 있게 도와줍니다. 이로 인해 코드가 더 깔끔해지고 재사용성이 증가합니다.
  • Spring에서는 AOP를 이용해 메서드 호출 전후에 특정 처리를 추가할 수 있습니다.
@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Method called: " + joinPoint.getSignature().getName());
    }
}

 

 

3. 모듈화

  • Spring은 여러 개의 모듈로 구성되어 있어 필요한 기능만 선택적으로 사용할 수 있습니다. 예를 들어, Spring Data, Spring Security, Spring Web 등 다양한 서브모듈을 통해 애플리케이션의 요구에 맞는 기능을 추가할 수 있습니다.

4. 트랜잭션 관리

  • Spring은 선언적 트랜잭션 관리 기능을 제공하여, 비즈니스 로직에서 트랜잭션을 쉽게 관리할 수 있도록 도와줍니다. @Transactional 어노테이션을 사용해 메서드 또는 클래스 수준에서 트랜잭션을 자동으로 관리할 수 있습니다.

 

@Transactional
public void transferMoney(Account fromAccount, Account toAccount, double amount) {
    // 돈 이체 처리 로직
}

 

5. Spring MVC

  • Spring MVC는 웹 애플리케이션을 위한 모델-뷰-컨트롤러(MVC) 프레임워크입니다. 요청을 처리하고, 뷰를 렌더링하며, 사용자 인터페이스를 구축하는 데 필요한 기능을 제공합니다.
  • Spring MVC는 RESTful API 개발도 지원하여, REST 기반의 웹 서비스를 쉽게 구축할 수 있습니다.
@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello, World!";
    }
}

 

6. Spring Boot

  • Spring Boot는 Spring Framework의 복잡성을 줄여주는 자동 설정내장 서버를 제공합니다. 이를 통해 설정을 최소화하고, 독립 실행형 애플리케이션을 쉽게 만들 수 있습니다. Spring Boot는 Spring Cloud와 결합하여 마이크로서비스 아키텍처를 쉽게 구현할 수 있습니다.
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

 

7. Spring Security

  • Spring Security는 인증(Authentication)과 권한 부여(Authorization)를 처리하는 프레임워크로, 웹 애플리케이션에 필요한 보안 기능을 제공합니다. 로그인, 로그아웃, CSRF 방어, 권한 체크 등의 기능을 손쉽게 설정할 수 있습니다.

 

8. Spring Data

  • Spring Data는 데이터베이스와의 상호작용을 단순화하는 데 도움을 줍니다. 특히 JPA(Java Persistence API)를 사용하여 관계형 데이터베이스와의 통합을 돕고, CRUD 작업을 자동화합니다.
public interface PersonRepository extends JpaRepository<Person, Long> {
    List<Person> findByLastName(String lastName);
}

 

 

9. Spring Batch

  • Spring Batch는 대용량 데이터 처리 및 배치 작업을 처리하기 위한 프레임워크입니다. 데이터를 읽고, 처리하고, 쓰는 작업을 효율적으로 처리할 수 있도록 도와줍니다.

 

10. Spring Cloud

  • Spring Cloud는 마이크로서비스 아키텍처를 지원하는 다양한 도구를 제공합니다. 예를 들어, 서비스 디스커버리, 분산 설정 관리, API 게이트웨이, 회로 차단기 패턴 등을 지원합니다.

 

IoC (Inversion of Control) 객체 생성 및 의존성 관리(Dependency Injection)를 통해 애플리케이션의 결합도를 낮추고 유연성 제공
DI (Dependency Injection) 객체 간의 의존 관계를 자동으로 주입하여 코드의 유지보수성과 테스트 용이성 향상
AOP (Aspect-Oriented Programming) 공통 관심사를 분리하여 코드 재사용성을 높이고 비즈니스 로직과 분리된 처리 가능 (예: 트랜잭션 관리, 로깅)
모듈화 필요한 모듈만 선택적으로 사용할 수 있어 애플리케이션의 크기와 복잡도를 줄임
Spring MVC 웹 애플리케이션을 위한 모델-뷰-컨트롤러(MVC) 패턴 지원, RESTful API 개발 가능
Spring Boot 자동 설정과 내장 서버를 제공하여 설정을 간소화하고 독립 실행형 애플리케이션을 쉽게 만들 수 있음
트랜잭션 관리 선언적 트랜잭션 관리 지원, @Transactional 어노테이션을 통해 트랜잭션을 쉽게 처리
Spring Security 인증 및 권한 부여를 처리하며, 웹 애플리케이션의 보안을 쉽게 설정할 수 있음
Spring Data 데이터베이스와의 상호작용을 단순화하며, JPA, Hibernate와의 통합을 지원
Spring Batch 대용량 배치 작업 처리 지원, 데이터를 읽고 쓰는 배치 작업을 효율적으로 처리할 수 있음
Spring Cloud 마이크로서비스 아키텍처 지원, 서비스 디스커버리, API 게이트웨이 등 분산 시스템 구축에 필요한 도구 제공
강력한 커뮤니티 활발한 오픈소스 프로젝트로, 커뮤니티와의 상호작용을 통해 지속적으로 발전
유연성 다양한 기술과 쉽게 통합 가능, 애플리케이션 아키텍처에 맞게 확장 가능

 

[4]  Spring Framework 사용 시 장점:

  • 유연성: 다양한 기술과 쉽게 통합할 수 있으며, 다양한 애플리케이션 아키텍처에 맞게 확장 가능합니다.
  • 모듈화: 필요한 기능만 선택하여 사용할 수 있습니다.
  • 표준화: 널리 사용되는 표준을 기반으로 구축되어 있으며, 많은 라이브러리 및 프레임워크와 호환됩니다.
  • 강력한 커뮤니티: Spring은 활발한 오픈소스 프로젝트로, 커뮤니티와의 상호작용을 통해 지속적으로 발전합니다.

 

[5]  Spring Framework 사용 시 고려할 점:

  • 학습 곡선: 많은 기능이 포함되어 있어 초보자에게는 다소 복잡할 수 있습니다.
  • 구성 파일: 초기 설정이 다소 복잡할 수 있지만, Spring Boot로 자동 설정을 사용할 수 있습니다.

Spring Framework는 엔터프라이즈 애플리케이션을 위한 강력한 솔루션을 제공하며, 다양한 기능을 활용하여 효율적인 개발을 할 수 있도록 돕습니다.

 

 

'Back-End (Web) > Spring' 카테고리의 다른 글

[Spring] Spring MVC 패턴  (1) 2024.12.14
[Spring] Spring Boot  (0) 2024.12.10
[Spring] 웹 개발의 흐름  (1) 2024.12.05
[Spring] Spring 웹 애플리케이션 계층 구조  (2) 2024.11.13
[Spring] 스프링 웹 개발 기초  (0) 2024.11.13

트러블 슈팅

1. 배경

  • 드디어 웹 개발의 기초에 입문하였다.
  • 사용 기술은 spring, jsp, jdbc, mysql이다.
  • 웹 개발의 기초를 위한 일정표를 만드는 발제를 진행하였다.

2. 발단

  • 처음부터 끝까지 문제가 상당히 많았다.
  • 데이터의 타입 정의는 문제가 없었다
  • 예외처리에서 예외처리가 무한으로 재귀되는 문제가 있어 확인해 보았다.
  • 매핑 형식에 대한 오류가 등장하였다.
  • 데이터 로드 형식, 범위에 대한 문제가 발생하였다.
  • SQL문의 경우 오랜만에 사용하다보니, 조금 익숙한 감이 떨어졌다.

3. 전개&위기

1. Service

  • 이 파트는 단순히 클라이언트의 요청을 처리하는 파트였어서 큰 문제는 없었다.
  • 다만 지금 보니 파일이나 폴더명이 service로 되어있어야했다.

2. exception

  • 위와 같이 대분의 오류를 판단하고 메시지 형태로 페이지에 띄우는 코드를 작성했다.
  • 다만 여기서 문제가 있었던 것이, 맨 처음에는 페이지로 연동을 안 시켜주어서 에러를 띄우기 시작하였다.

3. controller

  • 여기 파트의 경우 데이터를 불러오고 처리하는 직접적인 메인에 가까운 코드였다.
  • 여기서는 다양한 문제가 발생하였는데, 데이터를 외래키를 사용하기 위해 테이블을 2개로 나누고나니, 요청에서 문제가 다수 발생하였다.
  • 그 이외에도 post,get만 사용하였지만 다양한 방식을 사용하지 못했다는 점도 문제로 잡혔다.
  • 가장 큰 문제는 사실 위의 문제보다는 2개가 나타났었다
  • 하나는, 그저 시연에 있던 코드를 정확히 이해하지 못하고 치기만 했다는 점에서 이해가 부족해 보인다.
  • 둘, 여기는 서버라서 그런건지 인터럽트가 안걸린다. 이게 엄청난 시간을 딜레이 시킨 요인이 되기 시작했다.

4. repository

  • 데이터를 불러오는 형식이나 양, 직접적인 jdbc를 이용한 sql문 처리를 담당했다.
  • 여기서 가장 큰 문제는 sql일것이라 생각했으나, 이미 해본 적이 있던 부분이라 차라리 할만했다.
  • 오히려 외래키가 들어오고 나서 부터 생기는 join 문제가 발목을 잡았다.
  • 가장 큰 문제였던 점은, mysql에서는 직접 데이터를 불러오고 삭제하지만 여기서는 다른 java코드에서 요청하다보니 정확히 어디서 요청하고 어디서 문제가 난 것인지 몰랐다는 점이 너무 시간을 잡아먹었다.

  • 여기는 딱히 막히는 부분은 없었지만, 저 위의 태그의 사용방식을 정확히 알지 못한게 좀 시간을 잡아먹었다.

5. 절정

1. controller

  • 처음에는 잘 몰랐지만, 하면서 controller의 역활은 생각보다 복잡하면서도 명확하다는 것을 파악할 수 있었다.
  • 우선 필요한 데이터를 수집하고, 데이터를 통해 문제를 처리한다. 혹시모를 예외를 처리하면서 다른 코드와 가장 밀접하게 많이 연동되는 곳이라는 것을 파악할 수 있었다.
  • 과제의 내용에 외래키를 꼭 사용하는 것이 포함되어 있었다보니, 원래의 코드를 수정하여 postRepository를 2가지 테이블에 같이 걸어 데이터를 제공받는 코드로 작성하였다.
  • 예외는 exception 테이블에 있는 GlobalExceptionHandler에서 처리할 수 있도록 예외를 해당 클래스와 연동된 페이지로 페이지 이동을 걸어두었다.
  • 모든 클래스는 요청하는 것이 어떤 데이터인지 형식인지의 차이는 있었지만 위의 형태를 유지하도록 코드를 구성했다.

 

2. repository

  • 여기서는 조인외에는 거의 다 데이터베이스 자체적인 오류로 인해 문제가 많이 발생하였다.
  • 외래키를 첨가하고 나서부터, user_id라는 외래키를 직접 입력을 받는 형식을 처음에는 사용하였으나,
  • 사실 옆 테이블의 기본키라는 것을 깨닫고 그냥 옆에서 데이터를 불러오는 형식으로 바꾸려 했더니 기존 코드와 충돌이 어마무시하게 일어났다.
  • user_id가 입력이 안되어서 무결성 오류로 데이터가 저장이 안되는 경우부터, 데이터가 한 테이블만 저장되고 다른 테이블에는 외래키로 인해 저장이 안되는 등 여러 문제가 있었다.
  • 결론적으로 대부분 외래키에 대한 무결성 보존 오류였다. 이 점은 항상 주의하는 수 밖에 없을 것 같다.

 

3. exception

  • 여기서 의존성 문제가 가장 많이 등장했던 것 같다. 
  • 다만 이 부분은 코딩적인 문제라기보단, 지식의 문제라 많이 하다보면 괜찮을 것 같다.

 

6. 결말

  • 이번 코드에서 걱정했던 클린 코드의 문제가 조금씩 드러나고 있는 것 같다.
  • DB,서버,클라 이렇게 3개가 통신하다보니 더 코드가 복잡하고 읽어내기 힘들었다고 생각한다.
  • 왜 사람들이 폴더를 정확히 나눠서 코드를 짜라는지 알 수 있었고 앞으로도 처음부터 잘 배치하는 것을 목표로 해야할 것 같다는 생각이 들었다.
  • 이번 코드하면서 테스트를 하면서 하진 않았다. 다만, 서버의 경우 반드시 테스트를 통해서 점검을 해야하는 점을 생각하면 추후 반드시 한번은 해봐야겠다.
  • 위의 에러들이 가장 알기 어려웠지만, 지금와서 보면 체계를 잘 잡는게 중요한 것 같다.
  • 웹 개발의 로드맵을 머리에 저장해 두면 앞으로 이런 문제는 좀 덜 생길 것 같다.
  • 하면서 가장 어려웠던게 어디서부터 어떤 순서로 만들어야하는지 감이 전혀 안왔기 때문이다.
  • 각 프로그램들이 하는 일에 대한 정의가 머리에서 흐릿하게만 떠올라서 배치하는 것 조차 어려웠기 때문이다.
  • 그리고 다양한 개념이 갑자기 쓰이다보니, 또 머리속에서 개념이 흔들리고 있다. 이 부분도 함께 채워야할 것 같다.

 

'Project > Spring' 카테고리의 다른 글

Spring Querydsl 과제 회고  (1) 2025.01.27
[Spring] IOC & DI  (0) 2025.01.23
스프링 NEWSFEED 협업 프로젝트  (1) 2024.12.27
일정표를 만들어 보자! 업데이트!  (2) 2024.12.19
[ Spring ] 쇼핑몰 프로젝트 회고  (0) 2024.11.25

Annotation

📌 코드에 메타데이터를 추가할 수 있는 기능을 제공하며 주로 코드에 특별한 의미를 부여하거나, 컴파일러와 런타임에 특정 동작을 트리거하기 위해 사용된다.

  • 어노테이션은 코드에서 직접적인 로직 실행에 영향을 미치지 않지만, 코드의 의미를 설명하거나 추가적인 처리를 위해 사용됩니다.
  • "명함"를 생각하면 편하다. 이 명함은 "사람"에서 "프로그래머인 사람"이 된다. 사람이라는 정체성은 그대로이지만, 이 사람의 용도를 알 수 있다. 코드의 용도를 표시하며 실제로 컴파일러도 그 의미를 알지만 프로그램(사람) 자체에는 변화가 없다.

[1]  어노테이션 정의

  • 어노테이션은 @ 기호로 시작하며, 클래스, 메서드, 변수, 매개변수, 패키지 등에 추가할 수 있다.

[2]  내장 어노테이션

  • @Override
    • 메서드가 상위 클래스나 인터페이스의 메서드를 오버라이드하고 있음을 나타낸다.
      • 이때 컴파일러는 메서드가 실제로 오버라이드하고 있는지 확인한다.
  • @Deprecated
    • 해당 요소가 더 이상 사용되지 않음을 나타낸다.
    • 해당 어노테이션이 붙은 코드를 사용하면 컴파일 경고가 발생한다.
  • @SuppressWarnings
    • 컴파일러 경고를 억제한다.
      • 사용되지 않는 변수에 대한 경고를 무시할 수 있다.

[3]  사용자 정의 어노테이션

  • 개발자가 필요에 따라 직접 어노테이션을 정의할 수 있다.
  • 사용자 정의 어노테이션은 특정 메타데이터를 추가하거나,
  • AOP(Aspect-Oriented Programming) 같은 기술과 결합하여 다양한 기능을 구현할 수 있다.
    • AOP는 심화 주차에 배울 내용

 

Lombok

📌 Java에서 반복적인 코드를 줄여주는 라이브러리로, 코드의 가독성과 유지보수성을 높이는 데 도움이 됩니다. 주로 getter, setter, toString, equals, hashCode 메서드와 같은 보일러플레이트 코드를 자동으로 생성해줍니다.

 

[1]  Lombok 사용 시 장점:

  • 보일러플레이트 코드 감소: Lombok은 반복적인 코드(예: getter, setter, 생성자 등)를 자동으로 생성해 주어 코드가 간결해집니다.
  • 코드의 가독성 향상: 중요한 로직에 집중할 수 있어 코드가 더 깔끔하고 이해하기 쉬워집니다.
  • 생산성 향상: 반복적인 코드 작성에 소모되는 시간을 절약할 수 있습니다.

 

[2]   Lombok 사용 시 단점:

  • 자동 생성된 코드의 가시성 부족: Lombok은 컴파일 시 코드 생성이 이루어지기 때문에, IDE에서 코드가 어떻게 처리되는지 확인하기 어려운 경우가 있습니다.
  • 디버깅 어려움: Lombok이 생성한 코드는 실제로 파일에 존재하지 않기 때문에, 디버깅 시 자동으로 생성된 메서드를 추적하는 데 어려움이 있을 수 있습니다.

 

@Getter, @Setter

  • 클래스의 모든 필드에 대한 getter와 setter 메서드를 자동으로 생성한다.
  • 예시 코드
@Getter
@Setter
public class User {
    private String name;
    private int age;
  
  /** 아래 코드를 @Getter, @Setter 어노테이션이 생성해준다.
    public String getName() {
	    return name;
    }

    public void setName(String name) {
	    this.name = name;
    }
    
    public int getAge() {
	    return age;
    }

    public void setAge(int age) {
	    this.age = age;
    }
    **/
}

위 코드에서 getName(), setName(String name), getAge(), setAge(int age) 메서드가 자동으로 생성된다.



@ToString

  • 객체의 toString() 메서드를 자동으로 생성한다.
  • 기본적으로 클래스의 모든 필드를 포함하며, 특정 필드를 제외하거나 포맷을 지정할 수도 있다.
  • 예시코드
@ToString
public class User {
    private String name;
    private int age;
}
  • toString() 메서드는 객체를 String으로 변환해주는 역할을 수행한다.

@EqualsAndHashCode

  • equals()와 hashCode() 메서드를 자동으로 생성한다.
  • 객체의 동일성과 해시 코드를 정의하는데 사용된다.
  • 예시 코드
@EqualsAndHashCode
public class User {
    private String name;
    private int age;
}

 


 

@NoArgsConstructor, @AllArgsConstructor, @RequiredArgsConstructor

  • 기본 생성자를 생성한다.
  • 모든 필드를 매개변수로 하는 생성자를 생성한다.
  • 필수(final) 필드만을 매개변수로 하는 생성자를 자동으로 생성한다.
  • 예시 코드
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String name;
    private int age;
}

 

@Data

  • @Getter, @Setter, @ToString, @EqualsAndHashCode,@RequiredArgsConstructor를 한꺼번에 적용하는 어노테이션이다.
  • 주로 테스트 용도로 사용한다.
  • 예시 코드
@Data
public class User {
    private String name;
    private int age;
}

@Builder

  • 빌더 패턴을 적용해 객체를 생성할 수 있게 합니다. 복잡한 객체 생성에 유용하며, 필드 이름을 명시적으로 지정하면서 객체를 생성할 수 있다.
  • 예시 코드
@Builder
public class User {
    private String name;
    private int age;
}
  • 사용 예시
User user = User.builder()
                .name("John")
                .age(30)
                .build();

@Slf4j

  • 클래스에 로그를 남기기 위한 Logger 객체를 자동으로 생성한다.
  • 예시 코드
@Slf4j
public class UserService {
    public void logMessage() {
        log.info("This is a log message");
    }
}

 

Framework

📌 프레임워크는 특정 프로그래밍 작업을 수행하기 위한 기반 구조를 제공하는 도구입니다. 예를 들어, 웹 애플리케이션 개발을 위한 Spring이나 Django와 같은 프레임워크는 애플리케이션 아키텍처구조를 정의하고, 개발자가 해당 구조 내에서 작업할 수 있도록 도와줍니다.

  • 프레임워크는 frame(틀) work(일하다)의 합성어로 일하기 위한 틀을 제공한다. 개발자는 해당 틀에서 일을 해야 한다.
  • 라이브러리가 도화지라면 프레임워크는 채색북과 같다. 둘다 그림을 완성시키는 도구이지만, 도화지는 완전히 자유로운 디자인을 할 수 있고 채색북은 자유롭지는 못하지만 편하게 그림을 완성시킬 수 있다.

 

[1] 프레임워크의 주요 특징:

  • 구조 제공: 프레임워크는 애플리케이션 개발의 기본 뼈대를 제공합니다. 예를 들어, 어떤 파일을 어디에 두고, 어떻게 코드를 구성할지에 대한 규칙을 제시합니다.
  • 규칙과 흐름: 프레임워크는 개발자가 따를 일정한 흐름을 정의합니다. 즉, 개발자가 애플리케이션을 어떻게 구조화할지에 대한 가이드라인을 제공합니다.
  • 확장성: 프레임워크는 애플리케이션을 개발하는 데 있어 기능을 추가하거나 수정할 수 있는 방법을 제공합니다. 그러나 기본적으로는 프레임워크 내에서 정해진 규칙을 따라야 합니다.
  • 재사용성: 프레임워크는 많은 기능을 미리 구현해두어, 개발자는 이러한 기능을 재사용할 수 있습니다. 예를 들어, 데이터베이스 연결, 보안 관리, 사용자 인증 등이 미리 구현되어 있는 경우가 많습니다.

[2] 프레임워크의 예시:

  • 웹 개발 프레임워크:
    • Spring Framework (Java): 웹 애플리케이션을 만들 때 필요한 기본 구조와 규칙을 제공합니다. Spring은 의존성 주입, AOP, 보안, 데이터베이스 연동 등 여러 기능을 제공합니다.
    • Django (Python): Python으로 웹 애플리케이션을 개발할 때 사용하는 프레임워크로, 기본적인 웹 애플리케이션의 구조와 URL 처리, 데이터베이스 연동 등의 기능을 제공합니다.
    • Ruby on Rails (Ruby): Ruby 언어로 웹 애플리케이션을 빠르게 개발할 수 있도록 도와주는 프레임워크입니다. RESTful 방식의 API 설계와 모델-뷰-컨트롤러(MVC) 아키텍처를 따릅니다.
  • 모바일 앱 개발 프레임워크:
    • React Native: JavaScript를 사용하여 iOS와 Android에서 실행되는 네이티브 앱을 개발할 수 있게 해주는 프레임워크입니다.
    • Flutter: Google에서 만든 프레임워크로, Dart 언어를 사용해 크로스 플랫폼 애플리케이션을 개발할 수 있습니다.

[3]  장점

  • 개발 프로젝트에 일관된 구조를 제공하여 코드의 일관성과 가독성을 높여주며 팀 협업이 편해진다.
  • 기본적으로 필요한 기능과 도구를 제공하여 개발자들이 핵심 비즈니스 로직에 집중할 수 있다.
  • 보안 관련 기능을 기본적으로 제공하여, 보안 취약점을 방지하는 데 도움을 준다.
  • 통합된 테스트 환경과 도구를 제공하여 테스트를 쉽게 작성하고 실행할 수 있다.
  • 인기 있는 프레임워크는 방대한 커뮤니티 지원을 받으며, 다양한 문서를 활용할 수 있다. 

[4]  단점

  • 프레임워크는 굉장히 복잡한 구조를 가지기 때문에, 처음 익히는 데 시간이 많이 소요된다.
  • 프레임워크의 새로운 버전이 기존 코드와 호환되지 않을 수 있다.
  • 정해진 규칙과 구조를 따르게 강제하여 자유롭게 변경하기 어려울 수 있다.

 

 

Library

📌 특정 기능을 수행하는 코드의 모음으로, 개발자가 필요할 때 그 기능을 호출하여 사용할 수 있는 도구입니다. 라이브러리는 애플리케이션의 흐름을 제어하지 않으며, 개발자가 원하는 기능만 골라서 사용할 수 있습니다. 즉, 라이브러리는 필요한 도구를 제공하지만, 애플리케이션의 전체적인 흐름은 개발자가 주도합니다.

 

[1] 라이브러리의 주요 특징:

  • 선택적 사용: 라이브러리는 개발자가 필요한 기능을 원할 때 호출해서 사용할 수 있습니다. 개발자가 전체적인 흐름을 제어하고, 필요한 기능만 사용할 수 있습니다.
  • 재사용성: 특정 기능을 여러 번 사용할 수 있도록 기능을 모듈화하여 제공합니다. 예를 들어, 수학 계산, 문자열 처리, HTTP 요청 보내기 등의 기능을 쉽게 사용할 수 있습니다.
  • 독립적: 라이브러리는 보통 독립적으로 동작하며, 다른 라이브러리나 애플리케이션에 종속되지 않습니다.

[2] 라이브러리의 예시:

  • JavaScript 라이브러리:
    • jQuery: HTML 문서를 다루고, 이벤트를 처리하며, AJAX 요청을 보내는 등의 기능을 쉽게 사용할 수 있도록 도와주는 JavaScript 라이브러리입니다. jQuery를 사용하면 DOM 조작을 더 간단히 할 수 있습니다.
    • Lodash: 배열, 객체, 함수 등의 데이터를 쉽게 다룰 수 있는 JavaScript 유틸리티 라이브러리입니다. 반복적인 코드 작성을 줄여주고, 다양한 유틸리티 함수들을 제공합니다.
  • Python 라이브러리:
    • NumPy: 수치 계산을 위한 Python 라이브러리로, 다차원 배열행렬 연산을 지원하여 과학적 계산을 손쉽게 할 수 있습니다.
    • Pandas: 데이터를 다루고 분석하는 데 유용한 라이브러리로, 데이터 프레임(DataFrame) 구조를 사용하여 데이터 처리 및 분석을 쉽게 합니다.
  • Java 라이브러리:
    • Apache Commons: 다양한 유틸리티 기능을 제공하는 Java 라이브러리로, 문자열 처리, 파일 입출력 등 여러 가지 기능을 간편하게 사용할 수 있습니다.
    • Google Guava: Java에서 컬렉션, 캐시, 문자열 처리 등을 편리하게 처리할 수 있도록 돕는 라이브러리입니다.

[3] 라이브러리 사용의 장점:

  • 빠른 개발: 이미 구현된 기능을 가져다 쓸 수 있어 개발 속도가 빨라집니다.
  • 모듈화: 필요한 기능만 가져다 쓰므로 코드가 깔끔하고 모듈화가 잘 됩니다.
  • 다양한 기능: 특정 작업을 처리하는 다양한 라이브러리가 존재하여, 원하는 기능을 쉽게 찾아 사용할 수 있습니다.

 

[4] 라이브러리 사용의 단점:

  • 라이브러리가 업데이트 되거나 지원이 중단될 경우 문제가 발생할 수 있다.
  • 버전 호환성 문제로 인해 다른 라이브러리나 기존 코드와 충돌이 발생할 수 있습니다.
    • 생각보다 빈번하게 발생하는 문제
  • 불필요한 기능을 포함한 라이브러리를 사용하면 비효율적이다.
  • 라이브러리의 내부 구현을 직접 수정하기 어려워, 특정 요구 사항에 맞게 조정하기 힘들 수 있다.

 

라이브러리와 프레임워크의 차이점을 표로 정리하면 다음과 같습니다:

  라이브러리 프레임워크
제어의 흐름 개발자가 흐름을 제어하고, 필요한 기능을 호출 프레임워크가 흐름을 제어하고, 개발자는 그 안에서 작업
사용 방식 필요한 기능을 선택하여 사용 전체적인 구조를 따르고, 규칙에 맞춰 작업
의존성 독립적이며, 필요할 때마다 호출하여 사용 애플리케이션 구조에 강하게 의존
구조 제공 여부 특정 기능을 제공하지만, 전체 구조는 제공하지 않음 전체적인 애플리케이션 구조를 제공
사용 예 특정 기능만 필요할 때 (예: jQuery, Lodash) 애플리케이션 개발 시 기본적인 구조가 필요한 경우 (예: Spring, Django)
개발자 역할 개발자가 원하는 기능을 필요에 맞게 선택하고 적용 개발자는 프레임워크의 규칙에 따라 작업
예시 jQuery, NumPy, Google Maps API Spring, Django, Ruby on Rails

요약:

  • 라이브러리는 개발자가 필요한 기능을 선택하여 사용하는 도구이고, 흐름 제어는 개발자에게 있습니다.
  • 프레임워크는 애플리케이션의 구조와 흐름을 제공하며 라이브러리를 이미 포함하고 있습니다. 흐름 제어는 프레임워크가 담당합니다.

클린 코드

📌 클린 코드는 단순히 잘 동작하는 코드가 아니라, 가독성, 유지보수성, 확장성이 뛰어난 코드를 의미합니다. 이는 협업과 장기적인 코드 품질을 유지하기 위한 필수적인 개발 철학 입니다.

  • Dirty Code는 폭탄이다. A를 수정하니 B가 터지는 연쇄반응이 수시로 나온다.
  • 협업의 필수 조건으로, 사실 본인이 혼자해도 이 규칙을 어느정도 지켜야한다.
  • 기술 부채가 쌓이면 나중에는 결국 리펙토링이 아니라 재개발하는게 더 좋을 수도 있다.
  • 프로그래머는 가뜩이나 이직이 많아 에일리언코드(담당자가 없어져서든 버전의 변화로든 알 수 없는 코드를 말한다.)가 많이 생성될 가능이 높다.

감당 못함

 

리팩터링

📌 결과의 변경 없이 코드의 구조를 재조정하는 것을 말합니다. 주로 가독성을 높이고 유지보수를 편하게 하기 위해 추후 수정하는 것을 말합니다. 버그를 없애거나 새로운 기능을 추가하는 행위는 아닙니다.

  • 코드가 읽기 어려운 순간 (코드를 읽으면 바로 이해가 되어야한다.)
  • 수정할 때마다 오류가 발생하는 경우
  • 같은 코드가 여기저기 복붙되어 있는 경우
  • 확장하려는데 코드 구조가 방해되는 경우
  • 단위 테스트 작성이 어려운 경우

보통 위와 같은 상황에서 진행하게 된다.

 

 

 

클린 코드의 기본 원칙

의미 있는 이름 짓기
  1. 구체적이고 의도를 담은 이름을 사용
  2. 매직 넘버를 피하라
  3. 데이터의 의미를 이름에 반영
함수 분리 하기
  1. 하나의 함수는 하나의 역할만 수행 (sendEmail은 이메일 발송의 흐름만 관리)
  2. 복잡한 작업은 작은 함수로 분리
  3. 함수 이름은 동작과 목적을 명확히 표현
불필요한 주석 제거
  1. 주석은 코드가 아닌 의도를 설명
    • “어떻게”가 아닌 “왜”를 설명
  2. 주석 대신 명확한 변수와 함수 이름으로 의도를 드러냄
  3. 불필요한 주석은 제거하고, 코드는 가능한 자체적으로 읽히게 작성
코드 중복 제거
DRY (Don’t Repeat Yourself) 원칙을 준수

복잡한 코드를 단순화하기
  1. 조건문이 복잡하거나 여러 논리를 포함한다면 메서드로 분리
부정 표현을 긍정 표현으로 바꾸기
  1. 긍정적 변수명 사용
  2. 긍정적 조건문 작성
  3. 이중 부정 지양
else 문 사용 지양하기
  1. else 문을 피하고 기본 동작을 명시
  2. 전처리와 핵심 로직을 분리
  3. 각 조건은 독립적으로 처리

 

의미 있는 이름 짓기

// 나쁜 예
public void processData(List<String> data) {
    for (String item : data) {
        if (item.length() > 5) {
            System.out.println(item);
        }
    }
}

// 좋은 예
public void printLongUserNames(List<String> userNames) {
    final int MIN_NAME_LENGTH = 5;
    for (String userName : userNames) {
        if (userName.length() > MIN_NAME_LENGTH) {
            System.out.println(userName);
        }
    }
}
  1. 구체적이고 의도를 담은 이름을 사용
  2. 매직 넘버를 피하라
  3. 데이터의 의미를 이름에 반영

 

 

함수 분리 하기

// 나쁜 예
public void sendEmail(String recipient, String subject, String body) {
    if (recipient == null || recipient.isEmpty()) {
        throw new IllegalArgumentException("Recipient cannot be null or empty");
    }
    System.out.println("Connecting to SMTP server...");
    System.out.println("Authenticating...");
    System.out.println("Sending email to: " + recipient);
    System.out.println("Subject: " + subject);
    System.out.println("Body: " + body);
    System.out.println("Email sent successfully.");
}

// 좋은 예
public void sendEmail(String recipient, String subject, String body) {
    validateRecipient(recipient);
    connectToSmtpServer();
    authenticate();
    deliverEmail(recipient, subject, body);
}

private void validateRecipient(String recipient) {
    if (recipient == null || recipient.isEmpty()) {
        throw new IllegalArgumentException("Recipient cannot be null or empty");
    }
}

private void connectToSmtpServer() {
    System.out.println("Connecting to SMTP server...");
}

private void authenticate() {
    System.out.println("Authenticating...");
}

private void deliverEmail(String recipient, String subject, String body) {
    System.out.println("Sending email to: " + recipient);
    System.out.println("Subject: " + subject);
    System.out.println("Body: " + body);
    System.out.println("Email sent successfully.");
}
  1. ★  하나의 함수는 하나의 역할만 수행   (sendEmail은 이메일 발송의 흐름만 관리)
  2. 복잡한 작업은 작은 함수로 분리
  3. 함수 이름은 동작과 목적을 명확히 표현

 

 

불필요한 주석 제거

// 나쁜 예
public void processTransaction(Account fromAccount, Account toAccount, double amount) {
    // 송금 금액이 0보다 커야 합니다.
    if (amount <= 0) {
        throw new IllegalArgumentException("송금 금액은 0보다 커야 합니다.");
    }

    // 잔액 확인
    if (fromAccount.getBalance() < amount) {
        throw new IllegalStateException("계좌 잔액이 부족합니다.");
    }

    // 같은 계좌인지 확인
    if (fromAccount.equals(toAccount)) {
        throw new IllegalArgumentException("같은 계좌로 송금할 수 없습니다.");
    }

    // 송금 실행
    fromAccount.withdraw(amount); // 돈을 출금합니다.
    toAccount.deposit(amount);    // 돈을 입금합니다.

    // 송금 로그
    System.out.println("송금 성공: " + amount + "원 전송됨.");
}

// 좋은 예
public void processTransaction(Account fromAccount, Account toAccount, double amount) {
    // 비즈니스 규칙: 송금 금액은 0보다 커야 함
    if (amount <= 0) {
        throw new IllegalArgumentException("송금 금액은 0보다 커야 합니다.");
    }

    // 비즈니스 규칙: 송금 계좌 잔액이 부족하면 송금 불가
    if (fromAccount.getBalance() < amount) {
        throw new IllegalStateException("계좌 잔액이 부족합니다.");
    }

    // 비즈니스 규칙: 동일 계좌 간 송금 금지 (실수 방지 목적)
    if (fromAccount.equals(toAccount)) {
        throw new IllegalArgumentException("같은 계좌로 송금할 수 없습니다.");
    }

    // 송금 실행
    fromAccount.withdraw(amount);
    toAccount.deposit(amount);

    // 로그 기록: 성공적인 송금을 기록 (보안 및 추적 목적)
    System.out.println("송금 성공: " + amount + "원 전송됨.");
}
  1. 주석은 코드가 아닌 의도를 설명
    • “어떻게”가 아닌 “왜”를 설명
  2. 주석 대신 명확한 변수와 함수 이름으로 의도를 드러냄
  3. 불필요한 주석은 제거하고, 코드는 가능한 자체적으로 읽히게 작성
  4. 사실 주석은 함부로 쓰지 않는게 좋다. 추후 관리가 어렵고 주석은 전부 개발자가 직접 달아야한다보니 더 문제가 많이 생긴다.

 

코드 중복 제거

// 나쁜 예
public void printUserName(String name) {
    System.out.println("User: " + name);
}

public void printAdminName(String name) {
    System.out.println("Admin: " + name);
}

// 좋은 예
public void printName(String role, String name) {
    System.out.println(role + ": " + name);
}

DRY (Don’t Repeat Yourself) 원칙을 준수

 

 

복잡한 코드를 단순화하기

// 나쁜 예
if (user != null && user.getAge() > 18 && user.isActive()) {
    // do something
}

// 좋은 예
if (isActiveAdultUser(user)) {
    // do something
}

private boolean isActiveAdultUser(User user) {
    return user != null && user.getAge() > 18 && user.isActive();
}
  1. 조건문이 복잡하거나 여러 논리를 포함한다면 메서드로 분리
  2. isActiveAdultUser가 중요한 것 = 왜 했는지

 

 

부정 표현을 긍정 표현으로 바꾸기

// 나쁜 예
if (!user.isInActive()) {
    return "Inactive User";
}
return "Active User";

// 좋은 예
if (user.isActive()) {
    return "Active User";
}
return "Inactive User";
  1. 긍정적 변수명 사용   -   이거 상당히 중요하다
  2. 긍정적 조건문 작성
  3. 이중 부정 지양 = 이건 반의어를 생각해봐라, 진짜 웬만해서는 피해야한다.
  4. isinactive 라면 긍정인 isactive로 수정한다. 부정은 결국 한번 더 생각을 해야한다..

 

 

else 문 사용 지양하기

// 나쁜 예
public void login(User user) {
    if (user != null) {
        if (user.isActive()) {
            if (user.isVerified()) {
                System.out.println("Login successful");
            } else {
                System.out.println("User is not verified");
            }
        } else {
            System.out.println("User is inactive");
        }
    } else {
        System.out.println("Invalid user");
    }
}

// 좋은 예
public void login(User user) {
    if (user == null) {
        System.out.println("Invalid user");
        return;
    }
    if (!user.isActive()) {
        System.out.println("User is inactive");
        return;
    }
    if (!user.isVerified()) {
        System.out.println("User is not verified");
        return;
    }

    // 모든 조건을 통과한 경우
    System.out.println("Login successful");
}

// 더 좋은 예
public void login(User user) {
    String validationResult = validateUser(user);

    if (!validationResult.equals("Valid")) {
        System.out.println(validationResult);
        return;
    }

    // 모든 조건을 통과한 경우
    System.out.println("Login successful");
}

private String validateUser(User user) {
    if (user == null) {
        return "Invalid user";
    }
    if (!user.isActive()) {
        return "User is inactive";
    }
    if (!user.isVerified()) {
        return "User is not verified";
    }

    return "Valid";
}
  1. else 문을 피하고 기본 동작을 명시
  2. 전처리와 핵심 로직을 분리
  3. 각 조건은 독립적으로 처리

 

클린 코드 이론 원칙

 

클린 코드를 작성하는 데 있어 추가적으로 고려할 수 있는 원칙과 실천 사항은 다음과 같습니다:


1. 코드의 가독성 향상

  • 일관성 유지: 코드 스타일(들여쓰기, 괄호 배치, 공백 등)을 일관되게 유지합니다.
  • 짧은 함수: 함수는 가능한 짧게 유지하여 한눈에 이해할 수 있도록 합니다.
  • 코드 정렬: 논리적 흐름을 고려하여 코드를 정렬합니다(예: 관련된 부분끼리 묶기).

2. 객체 지향 설계 원칙(SOLID)

  • S - 단일 책임 원칙(Single Responsibility Principle)
    클래스나 모듈은 하나의 책임만 가져야 합니다.
  • O - 개방-폐쇄 원칙(Open/Closed Principle)
    코드는 확장에는 열려 있고, 수정에는 닫혀 있어야 합니다.
  • L - 리스코프 치환 원칙(Liskov Substitution Principle)
    서브클래스는 언제나 기반 클래스의 역할을 대체할 수 있어야 합니다.
  • I - 인터페이스 분리 원칙(Interface Segregation Principle)
    클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 합니다.
  • D - 의존성 역전 원칙(Dependency Inversion Principle)
    상위 모듈은 하위 모듈에 의존해서는 안 됩니다. 둘 다 추상화에 의존해야 합니다.

3. 에러 처리

  • 명확한 예외 처리: 예외를 포괄적으로 잡기보다는 특정한 경우에만 잡아야 합니다.
  • 사전 조건 체크: 함수나 메서드의 입력값 검증을 통해 에러를 방지합니다.
  • null 사용 지양: null 대신 Optional, Maybe 같은 명시적 표현을 사용합니다.

4. 의존성 관리

  • 의존성 주입(DI): 객체 생성과 사용을 분리하여 의존성을 줄입니다.
  • 전역 상태 지양: 전역 변수를 사용하지 않고 명시적으로 데이터를 전달합니다.

5. 성능 및 최적화

  • 불필요한 작업 최소화: 반복 작업이나 연산을 줄이고 캐싱을 활용합니다.
  • 지연 계산(lazy evaluation): 필요할 때만 계산하도록 설계합니다.
  • 효율적인 자료구조 선택: 목적에 맞는 자료구조를 사용하여 성능을 개선합니다.

6. 테스트 가능 코드

  • 단위 테스트 작성: 각 함수와 모듈의 동작을 검증합니다.
  • 테스트 자동화: CI/CD 환경에서 자동화된 테스트를 실행합니다.
  • 모의 객체(Mock Object) 활용: 외부 의존성을 분리하여 테스트를 단순화합니다.

7. 유지보수성을 고려한 설계

  • 코드 중복 제거: 유틸리티 함수나 상수로 공통된 로직을 추출합니다.
  • 확장성 있는 설계: 변경 사항이 최소한의 영향으로 처리되도록 설계합니다.
  • 의도를 드러내는 코드: 코드만 읽어도 설계 의도를 알 수 있게 작성합니다.

8. 클린 아키텍처

  • 계층 분리: 비즈니스 로직, 프레젠테이션, 데이터 접근 계층을 분리합니다.
  • 엔티티 독립성: 엔티티는 프레임워크나 데이터베이스에 의존하지 않도록 설계합니다.
  • 경계(Interface) 설정: 외부 의존성과 내부 로직 사이에 명확한 경계를 만듭니다.

9. 적절한 캡슐화

  • 정보 은닉: 클래스 내부 상태를 외부에서 직접 접근하지 못하도록 합니다.
  • Getter/Setter 최소화: 단순히 데이터를 노출하는 Getter/Setter보다는 행동 중심의 메서드 사용.

10. 명확한 의사소통

  • 코드 리뷰: 동료와의 코드 리뷰를 통해 더 나은 코드를 작성합니다.
  • 컨벤션 문서화: 팀에서 사용하는 코딩 스타일을 문서로 명시합니다.

개방-폐쇄 원칙 (Open-Closed Principle)

📌 객체 지향 설계의 핵심 원칙 중 하나로, 소프트웨어 모듈, 클래스, 또는 함수가 다음과 같은 두 가지 상태를 만족해야 한다는 개념

  1. 확장에는 열려 있어야 한다 (Open for extension):
    • 새로운 기능을 추가하거나 요구사항의 변화에 따라 시스템의 동작을 확장할 수 있어야 한다.
    • 기존 코드를 수정하지 않고도 기능 추가가 가능해야 한다.
  2. 수정에는 닫혀 있어야 한다 (Closed for modification):
    • 기존의 잘 검증된 코드는 수정되지 않아야 한다.
    • 코드 변경이 없기 때문에 기존 시스템의 안정성을 유지할 수 있다.

목표

  • 코드를 재사용 가능하고 유지보수성이 높은 상태로 유지.
  • 새로운 요구사항이 생기더라도 기존 코드의 변경 없이 대응 가능.
class Notification {
    public void send(String type) {
        if (type.equals("Email")) {
            // 이메일 전송 코드
        } else if (type.equals("SMS")) {
            // SMS 전송 코드
        }
    }
}

--------------------------------------------

interface Notification {
    void send();
}

class EmailNotification implements Notification {
    public void send() {
        // 이메일 전송 코드
    }
}

class SMSNotification implements Notification {
    public void send() {
        // SMS 전송 코드
    }
}

class PushNotification implements Notification {
    public void send() {
        // 푸시 알림 전송 코드
    }
}

class NotificationService {
    public void sendNotification(Notification notification) {
        notification.send();
    }
}

새로운 알림 방식(예: 푸시 알림)을 추가하려면 send 메서드에 조건문을 추가해야 하므로 기존 코드를 수정해야 함.

=> 새로운 알림 방식을 추가하려면 새로운 클래스를 구현하기만 하면 된다. 기존 코드는 수정하지 않는다.

 

 

 

OOP (Object-Oriented Programming)

📌 객체 지향 프로그래밍(OOP)은 현실 세계의 사물이나 개념을 객체(Object)로 모델링하여 소프트웨어를 개발하는 방식

  • 객체를 사용해 데이터를 묶고, 이를 다루는 기능을 추가합니다.

[1] 캡슐화 (Encapsulation)

  • 내부 데이터를 보호하고, 외부에서 접근할 방법을 제한하는 것.
    • 약병은 뚜껑으로 약을 보호하지만, 우리가 약을 꺼낼 수 있는 기능(뚜껑 열기)을 제공해요.

[2] 상속 (Inheritance)

  • 부모가 가진 것을 자식이 물려받는 것.
    • 부모가 물려준 성격(코드)을 자식이 사용하거나 더 발전시킬 수 있어요!

[3] 다형성 (Polymorphism)

  • 같은 동작을 다양한 방식으로 처리할 수 있는 것.
    • “달려!“라는 명령에 강아지는 뛰고, 자동차는 굴러가요. 하지만 둘 다 “달리는 동작”이에요!

[4] 추상화 (Abstraction)

  • 복잡한 내부 내용은 숨기고, 필요한 부분만 드러내는 것.
    • 자동차의 내부 작동 방식은 몰라도, 운전대와 페달로 운전할 수 있잖아요!

 

SOLID 원칙 - 면접에서 자주 나온다

📌 OOP를 더 잘 설계하기 위한 5가지 규칙.

  • 쉽게 고치고, 확장할 수 있는 코드를 만드는 지침.

[1]  S - 단일 책임 원칙

  • 클래스는 하나의 역할만 가져야 한다.
    • 주방은 요리만, 침실은 잠만. (하나의 역할만!)
  • 왜?
    • 역할이 여러 개면 고칠 때 어디를 수정해야 할지 복잡해 짐.
public class User {
		private String name; // 사용자 정보
    public void login() { /* 로그인 기능 */ }
    public void saveUser() { /* 데이터베이스 저장 기능 */ }
}

--------------------------

public class User { /* 사용자 정보 관리 */ }

public class AuthService {
    public void login(User user) { /* 로그인 기능 */ }
}

public class UserRepository {
    public void saveUser(User user) { /* 데이터베이스 저장 */ }
}

[2]  O - 개방/폐쇄 원칙

  • 코드는 확장에 열려 있고, 수정에는 닫혀 있어야 한다.
    • 옥상에 방을 추가해도 기존 방은 그대로. (확장 가능!)
  • 왜?
    • 기존 코드를 수정하면 예기치 못한 문제가 생길 수 있음.

원래라면 새로운 도형이 추가될 때마다 AreaCalculator 클래스를 수정해야 한다. 하지만 개방 폐쇄 원칙을 적용하면 새로운 도형이 추가되더라도 shape 인터페이스만 구현하면 된다.

 

  • 다형성을 활용하여 해결한다.
    • 인터페이스를 implements 하여 구현한 새로운 클래스를 만들어서 새로운 기능을 구현한다.
    • 역할(도형)과 구현(원, 사각형, 삼각형 등)을 분리하면 된다.
public class Shape {
    public String type;
}

public class AreaCalculator {
    public double calculate(Shape shape) {
        if (shape.type.equals("circle")) {
            return /* 원의 넓이 계산 */;
        } else if (shape.type.equals("square")) {
            return /* 사각형의 넓이 계산 */;
        }
    }
}

------------------------

public interface Shape {
    double calculateArea();
}

public class Circle implements Shape {
    public double calculateArea() { return /* 원의 넓이 계산 */; }
}

public class Square implements Shape {
    public double calculateArea() { return /* 사각형의 넓이 계산 */; }
}

public class AreaCalculator {
    public double calculate(Shape shape) {
        return shape.calculateArea();
    }
}

문제점

// Circle을 계산하는 경우
public class Main {
		public static void main(String[]) {
		
				AreaCalculator areaCalculator = new AreaCalculator();
				Circle circle = new Circle();
				
				areaCalculator.calculate(circle);
			
		}
}

// Square를 계산하는 경우
public class Main {
		public static void main(String[]) {
		
				AreaCalculator areaCalculator = new AreaCalculator();
				// Circle circle = new Circle();
				Square square = new Square();
				
				areaCalculator.calculate(square);
			
		}
}
  • 구현 객체를 변경하기 위해서는 해당 코드를 사용하는 클라이언트측의 코드를 변경해야 한다.
  • 객체의 생성, 사용 등을 자동으로 설정해주는 무엇인가가 필요하다.
    • Spring Container의 역할

 

[3]  L - 리스코프 치환 원칙

  • 자식 클래스는 부모 클래스를 대체할 수 있어야 한다.
    • 새가 날 수 있는 것처럼, 모든 새는 ‘날기’ 동작을 가져야 한다. (일관성 유지!)
    • 부모 클래스를 사용하는 곳에서 자식 클래스를 사용해도 프로그램의 동작에 문제가 없어야 한다.
  • 왜?
    • 부모 클래스처럼 동작하지 않으면, 프로그램이 예외를 일으킴.

 

  • 예시
    • ElectricCar는 Car 클래스를 상속 받았지만, accelerate() 를 사용할 수 없다. LSP 위반

 

리스코프 치환 원칙 적용

  • 인터페이스를 구현한 구현체를 믿고 사용할 수 있도록 만들어준다.
  • 엑셀은 앞으로 가는 기능이다. 만약 뒤로 간다면 LSP를 위반한다.
class Car {
    public void accelerate() {
        System.out.println("자동차가 휘발유로 가속합니다.");
    }
}

class ElectricCar extends Car {
    @Override
    public void accelerate() {
        throw new UnsupportedOperationException("전기차는 이 방식으로 가속하지 않습니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        car.accelerate(); // "자동차가 가속합니다."

        Car electricCar = new ElectricCar();
        electricCar.accelerate(); // UnsupportedOperationException 발생
    }
}

---------------------------------

// 가속 기능(역할)을 인터페이스로 분리
interface Acceleratable {
    void accelerate();
}

class Car implements Acceleratable {
    @Override
    public void accelerate() {
        System.out.println("내연기관 자동차가 가속합니다.");
    }
}

class ElectricCar implements Acceleratable {
    @Override
    public void accelerate() {
        System.out.println("전기차가 배터리로 가속합니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        Acceleratable car = new Car();
        car.accelerate(); // "내연기관 자동차가 가속합니다."

        Acceleratable electricCar = new ElectricCar();
        electricCar.accelerate(); // "전기차가 배터리로 가속합니다."
    }
}

[4]  I - 인터페이스 분리 원칙

  • 클래스는 필요 없는 기능을 강요받지 말아야 한다.
    • 로봇은 먹지 않는데, ‘먹기’ 기능을 구현해야 한다면 비 효율적.
  • 왜?
    • 쓰지 않는 기능 때문에 코드가 불필요하게 복잡해 짐.
  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
    • 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.
    • 즉, 하나의 큰 인터페이스보다는 여러 개의 작은 인터페이스로 분리해야 한다.
public interface Animal {
    void fly();
    void run();
    void swim();
}

public class Dog implements Animal {
    public void fly() { /* 사용하지 않음 */ }
    public void run() { /* 달리기 */ }
    public void swim() { /* 수영 */ }
}


------------------------------


public interface Runnable {
    void run();
}

public interface Swimmable {
    void swim();
}

public class Dog implements Runnable, Swimmable {
    public void run() { /* 달리기 */ }
    public void swim() { /* 수영 */ }
}

[5]  D - 의존 역전 원칙

  • “구체적인 것보다, 추상적인 것에 의존하라.”
    • 예: 모든 가전제품은 콘센트만 꽂으면 작동. (표준 인터페이스!)
  • 왜?
    • 변경에 유연해지고, 재사용성이 높아짐.
  • 예시
    • NotificationService는 EmailNotifier 클래스를 의존한다.
// Email 알림 클래스
class EmailNotifier {
    public void sendEmail(String message) {
        System.out.println("Email 알림: " + message);
    }
}

// 알림 시스템
class NotificationService {
    private EmailNotifier emailNotifier;

    public NotificationService() {
		    // 구체적인 클래스인 EmailNotifier에 의존
        this.emailNotifier = new EmailNotifier();
    }

    public void sendNotification(String message) {
        emailNotifier.sendEmail(message);
    }
}

public class Main {
    public static void main(String[] args) {
        NotificationService service = new NotificationService();
        service.sendNotification("안녕하세요! 이메일 알림입니다.");
    }
}

[이메일 알림이 아닌 SMS 알림과 같은 기능이 추가되면 NotificationService 는 수정되어야 한다. DIP 위반]
-----------------------------------


// 알림 인터페이스(추상화)
interface Notifier {
    void send(String message);
}

// Email 알림 클래스
class EmailNotifier implements Notifier {
    @Override
    public void send(String message) {
        System.out.println("Email 알림: " + message);
    }
}

// SMS 알림 클래스
class SMSNotifier implements Notifier {
    @Override
    public void send(String message) {
        System.out.println("SMS 알림: " + message);
    }
}

// 알림 서비스 (높은 수준 모듈)
class NotificationService {
		// 추상화된 인터페이스에 의존
    private Notifier notifier;

    // 의존성 주입 (생성자를 통해 주입)
    public NotificationService(Notifier notifier) {
        this.notifier = notifier;
    }

    public void sendNotification(String message) {
		    // notifier가 어떤 구현체인지 상관하지 않음
        notifier.send(message);
    }
}

public class Main {
    public static void main(String[] args) {
        // Email 알림을 사용
        Notifier emailNotifier = new EmailNotifier();
        NotificationService emailService = new NotificationService(emailNotifier);
        emailService.sendNotification("안녕하세요! 이메일 알림입니다.");

        // SMS 알림을 사용
        Notifier smsNotifier = new SMSNotifier();
        NotificationService smsService = new NotificationService(smsNotifier);
        smsService.sendNotification("안녕하세요! SMS 알림입니다.");
    }
}
  • 추상화된 Notifier 인터페이스에만 의존한다.
    • 새로운 알림 방식이 추가되어도 NotificationService 는 변경되지 않아도 된다.
  • 필요한 Notifier 객체를 외부에서 주입받는다.
    • NotificationService는 어떤 알림 방식을 사용할지에 대한 세부 사항을 몰라도 되므로, 의존성이 약해진다.
  • 모듈간의 결합도를 낮추고 유연성과 확장성을 높일 수 있다.
  • 서로의 변경 사항에 독립적이어서 변경에 유연하다.

 

 

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

📌 객체 지향 설계의 5가지 원칙(SOLID) 중 하나로, 상위 수준의 모듈(비즈니스 로직이나 주요 흐름을 결정하는 코드)과 하위 수준의 모듈(구체적인 구현 코드) 간의 의존성을 분리하고, 둘 다 추상화에 의존하도록 만드는 원칙입니다.

 

= 말이 어렵지만, 추상화=공통의 기능,주제는 따로 분리하는 것 / 의존성=특정 불변의 기능 혹은 상수에 의해 코드가 유연성이 떨어지는것

= 코드는 구체적인 클래스를 구성하기 전, 공통의 기능을 가진 요소에 대해 인터페이스로 미리 추상화를 거쳐야, 추후 응용이나 변형이 쉽다는 것, 더 쉽게 말하면 그냥 추상화를 잘 유념해라

핵심 개념

  1. 상위 모듈은 하위 모듈에 의존해서는 안 된다:
    • 상위 모듈은 구체적인 구현이 아닌, 추상화(인터페이스 또는 추상 클래스)에 의존해야 한다.
  2. 추상화는 세부사항에 의존하지 않는다:
    • 구체적인 구현(세부사항)이 추상화에 의존해야 하며, 그 반대는 아니다.

 

목표

  • 시스템을 더 유연하고 확장 가능하게 설계.
  • 변경이 필요한 경우, 한 모듈의 수정이 다른 모듈에 영향을 미치는 것을 방지.
  • 고수준의 로직과 저수준의 세부사항을 분리하여 시스템의 안정성과 재사용성 향상.

 

class FileLogger {
    public void log(String message) {
        System.out.println("Logging to a file: " + message);
    }
}

class Application {
    private FileLogger logger;

    public Application() {
        logger = new FileLogger(); // 구체적 구현에 의존
    }

    public void run() {
        logger.log("Application started.");
    }
}


-----------------------------------

interface Logger {
    void log(String message);
}

class FileLogger implements Logger {
    public void log(String message) {
        System.out.println("Logging to a file: " + message);
    }
}

class DatabaseLogger implements Logger {
    public void log(String message) {
        System.out.println("Logging to a database: " + message);
    }
}

class Application {
    private Logger logger;

    public Application(Logger logger) {
        this.logger = logger; // 추상화에 의존
    }

    public void run() {
        logger.log("Application started.");
    }
}

// 사용:
Logger logger = new FileLogger();
Application app = new Application(logger);
app.run();

Application 클래스는 FileLogger의 구체적인 구현에 의존하기 때문에 다른 로거로 변경하려면 Application 코드를 수정해야 합니다.

 

=> 이제 로깅 구현을 교체하려면 Logger를 구현하는 다른 클래스를 제공하기만 하면 된다. Application은 수정이 필요하지 않다.

 

의존성 역전 원칙과 의존성 주입

  • 의존성 역전 원칙은 설계 원칙이고, **의존성 주입(Dependency Injection)**은 이를 구현하는 방법론 중 하나입니다.
  • 의존성 주입 방식:
    1. 생성자 주입(Constructor Injection)
    2. 세터 주입(Setter Injection)
    3. 인터페이스 주입(Interface Injection)

적용 사례

  • DI 프레임워크: 스프링(Spring), 구아바(Guice) 등에서 제공하는 의존성 관리 기능은 DIP를 기반으로 설계되었습니다.
  • 디자인 패턴: 전략 패턴(Strategy Pattern), 팩토리 패턴(Factory Pattern) 등에서 활용됩니다.

 

실무에서는 추상화 과정에서 비용(시간)이 발생하기 때문에 기능을 확장할 가능성이 없다면 구현 클래스를 직접 사용하고 추후 변경된다면 인터페이스로 리팩토링 하면된다.

'기본기' 카테고리의 다른 글

클린 아키텍처 (Clean Architecture)  (1) 2025.02.23
테스트와 개발  (0) 2025.02.21
리팩토링  (0) 2025.02.15
백엔드 로드맵  (0) 2025.01.06
이름 짓기 규칙  (0) 2024.12.05

+ Recent posts