HTTP

HTTP는 HyperText Transfer Protocol 의 약자로
클라이언트서버 간 통신을 위한 통신 규칙 세트 또는 프로토콜이다.


프로토콜이란 컴퓨터 네트워크나 통신 시스템에서 데이터를 주고받는 데 사용되는 규약이다.
즉, 서로 상호간 원활한 통신을 위해 동일하게 어떠한 규칙을 설정한다는 의미이다.


HTTP는 주로 웹 브라우저가 웹 서버로부터 웹 페이지를 요청하고 받아오는 데 사용된다.
HTTP는 텍스트 기반의 프로토콜로 클라이언트가 요청(Request)하면 서버가 응답(Response)을 반환하는 방식으로 동작한다.


HTTP는 주로 다음과 같은 메서드를 사용하여 요청(Request)을 전송한다.

  • GET : 서버에서 리소스를 요청하는 메서드
  • POST : 클라이언트가 데이터를 서버로 보내는 메서드
  • PUT : 서버에 데이터를 전송하여 리소스를 업데이트하는 메서드
  • DELETE : 서버에서 리소스를 삭제하는 메서드

마찬가지로 서버는 다양한 HTTP 응답(Response)을 전송한다.

  • 200 - OK(정상)
  • 400 - Bad Request(잘못된 요청)
  • 404 - Resource Not Found (리소스를 찾을 수 없음)
  • 500 Internal Server Error(내부 서버 에러)

더 자세히 알고 싶다면 https://en.wikipedia.org/wiki/List_of_HTTP_status_codes를 확인해보면 된다.

 

HTTPS

HTTPSHyperText Transfer Protocol Secure의 약자로

HTTP의 확장 버전 또는 더 안전한 버전이라고 볼 수 있다.
HTTPS는 브라우저와 서버가 데이터를 전송하기 전에 안전하고 암호화된 연결을 설정한다.

 

HTTP는 기본적으로 암호화되지 않은 데이터를 전송한다. 즉, 브라우저에서 전송된 정보가 해킹될 위험이 있다. 이러한 상황을 막기 위해서 통신에 또 다른 보안 계층을 추가하기 위해서 HTTPS로 확장되었다.
HTTPS는 HTTP 요청 및 응답을 SSL 및 TLS 기술에 결합한다.

HTTPS 웹 사이트는 독립된 인증 기관(CA)에서 SSL/TLS 인증서를 획득해야 한다.
이러한 웹 사이트는 신뢰를 구축하기 위해 데이터를 교환하기 전에 브라우저와 인증서를 공유한다.
SSL 인증서는 암호화 정보를 포함하므로 서버와 웹 브라우저는 암호화 된 데이터를 교환할 수 있다.

 

HTTPS는 다음과 같이 동작한다.

  1. 사용자 브라우저의 주소 표시줄에 https:// URL 형식을 입력하여 HTTPS 웹 사이트를 방문한다.
  2. 브라우저는 서버의 SSL 인증서를 요청하여 사이트의 신뢰성을 검증하려고 시도한다.
  3. 서버는 퍼블릭 키가 포함된 SSL 인증서를 회신으로 전송한다.
  4. 웹 사이트의 SSL 인증서는 서버 아이덴티티를 증명한다. 브라우저에서 인증되면, 브라우저가 퍼블릭 키를 사용하여 비밀 세션 키가 포함된 메시지를 암호화하고 전송한다.
  5. 웹 서버는 프라이빗 키를 사용하여 메시지를 해독하고 세션 키를 검색한다. 그런 다음, 세션 키를 암호화하고 브라우저에 승인 메시지를 전송한다.
  6. 이제 브라우저와 웹 서버 모두 동일한 세션 키를 사용하여 메시지를 안전하게 교환하도록 전환한다.

HTTPS를 확인하기 위해서는 브라우저에서 웹 사이트 URL 옆에 있는 자물쇠 모양의 아이콘을 보면 된다.
최근에는 거의 대부분의 웹 사이트가 HTTPS를 제공한다.
자세한 통계를 보고 싶다면 https://transparencyreport.google.com/https/overview?hl=ko 를 참고하기 바란다.

 

SSL/TLS 인증서란?

SSL/TLS 인증서는 웹사이트와 사용자 사이의 데이터 통신을 보호하기 위한 보안 프로토콜인 Secure Sockets Layer 통칭 SSL(보안 소켓 레이어) 또는 Transport Layer Security 통칭 TLS(전송 계층 보안)의 일부로 사용되는 디지털 인증서이다. 이 인증서는 웹 브라우저와 웹 서버 간의 통신을 암호화하고 보호하여 제3자가 중간에서 데이터를 엿보거나 조작하는 것을 방지한다.

 

SSL/TLS 인증서의 최대 유효 기간은 13개월으로, 보안 위험을 줄이기 위해 점진적으로 단축되고 있다.

SSL/TLS 인증서에는 다음과 같은 내용이 포함되어 있다.

  • 도메인 이름
  • 인증 기관
  • 인증 기관의 디지털 서명
  • 발급 날짜
  • 만료 날짜
  • 퍼블릭 키
  • SSL/TLS 버전

최신 SSL/TLS 인증서는 SSL/TLS 대신 TLS 프로토콜을 사용하지만 SSL/TLS는 보안 전문가들 사이에서 널리 사용되는 약어이다. 엄밀하게 말하면 서로 다르지만, SSL과 TLS라는 용어는 일반적으로 같은 의미로 사용된다.

 

HTTP와 HTTPS의 차이점

 

  HTTP HTTPS
기본 프로토콜 HTTP/1과 HTTP/2 : TCP/IP 프로토콜
HTTP/3 : UDP기반 QUIC 프로토콜
SSL/TLS + HTTP/2 사용
포트 기본 포트 80 기본 포트 443
용도 이전 텍스트 기반 웹 사이트 모든 최신 웹 사이트
보안 추가 보안 기능 없음 퍼블릭 키 암호화에 SSL 인증서 사용
장점 인터넷을 통한 통신 지원 웹 사이트에 대한 권위, 신뢰성 및 검색 엔진 순위 개선

 

 

Reference

https://aws.amazon.com/ko/compare/the-difference-between-https-and-http/

 

HTTP와 HTTPS 비교 - 전송 프로토콜 간의 차이점 - AWS

1996~1997년에 출시된 최초의 HTTP 버전이 HTTP/1.1입니다. HTTP/2와 HTTP/3은 프로토콜 자체를 업그레이드한 버전입니다. 데이터 전송 시스템을 수정하면서 효율성을 개선했습니다. 예를 들어, HTTP/2는 텍

aws.amazon.com

https://aws.amazon.com/ko/what-is/ssl-certificate/

 

SSL 인증서란 무엇인가요? - SSL/TLS 인증서 설명 - AWS

AWS Certificate Manager(ACM)는 AWS 서비스 및 연결된 내부 리소스에 사용할 공인 및 사설 SSL/TLS 인증서를 손쉽게 프로비저닝, 관리 및 배포할 수 있도록 지원하는 서비스입니다. ACM은 SSL/TLS 인증서를 구

aws.amazon.com

 

'Computer Science > Network' 카테고리의 다른 글

HTTP의 특징  (0) 2023.09.19
URI 와 웹 브라우저 요청 흐름  (0) 2023.09.19
HTTP 웹 기본 지식  (2) 2023.09.18
로드 밸런싱(Load Balancing)  (0) 2023.08.29
SSL/TLS HandShake  (0) 2023.08.29

본 내용은 해당 강의를 듣고 필요에 맞게 요약한 글입니다.

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢

www.inflearn.com

 

먼저 스프링은 무엇인가? 부터가 강의의 시작이다.

여기서 말하는 무엇이냐는
단순히 스프링은 웹 프레임 워크고, DI 컨테이너이고 이런 걸 의미하는 게 아니라
스프링이 왜 만들어졌으며, 스프링의 핵심 개념은 무엇인가이다.
정말 스프링의 기원부터 설명을 해주셨다.

스프링은 영어 단어 뜻 그대로 이라는 단어를 뜻하며, 추운 겨울이 지나고 이 온다는 걸 뜻한다.


추운 겨울이란 무슨 뜻일까?


스프링 이전에 J2EE를 활용한 자바의 표준 기술인 EJB(Enterprise Java Bean)라는 기술을 사용했는데 해당 기술은 기술이 너무 느리고, 복잡하며, 테스트에도 많은 시간이 걸렸다. 또한 EJB에 의존하면서 코드를 짜야하는 것도 큰 문제였다.


그래서 이런 문제를 해결하기 위해서 두 명의 개발자가 오픈 소스를 만들었다.


그 두 사람이 바로 스프링(Spring)의 창시자 Rod Johnson(로드 존슨)하이버네이트(Hibernate)의 창시자 Gavin King(게빈 킹)이다.

 

로드 존슨은 EJB보다 단순하고 좋게 개발할 수 있다고 EJB의 문제점을 지적하면서 EJB 없이도 충분히 고품질의 확장 가능한 애플리케이션의 개발이 가능하다고 예제 코드를 선보이며 책을 냈는데 이 책이 큰 호응을 얻게 된다.
책 출간 직후, Juergen Hoeller(유겐 휠러), Yann Caroff(얀 카로프)가 로드 존슨에게 오픈소스 프로젝트를 제안하는데 이렇게 나오게 된 프로젝트가 바로 스프링이다. 스프링은 J2EE(EJB)라는 겨울을 넘어 새로운 시작(봄)이라는 뜻으로 이름이 지어졌다.

하이버네이트는 자바 객체-관계 매핑(ORM)을 위한 프레임워크로, EJB의 엔티티빈 기술을 대체하며, JPA(Java Persistence API)에 새로운 표준을 정의하였다.

 

스프링이란?

 

현재 스프링이라는 단어는 세 가지 정도 의미를 뜻할 수 있다.

  • 스프링 DI 컨테이너 기술
  • 스프링 프레임워크
  • 스프링 부트, 스프링 프레임워크 등을 모두 포함한 스프링 생태계

 

스프링의 핵심 개념


아무리 복잡한 기술이어도 이 기술의 핵심 콘셉트는 단순하다.

스프링의 핵심 개념은 스프링은 자바 언어 기반의 프레임 워크이다.
자바의 핵심 특징은 객체 지향 언어이고, 스프링은 객체 지향 언어가 가진 강력한 특징을 살려내는 프레임워크다.


즉 스프링은 좋은 객체 지향 애플리케이션을 개발할 수 있게 도와주는 프레임워크라고 할 수 있다.

 

스프링 프레임워크

  • 핵심 기술: 스프링 DI 컨테이너, AOP, 이벤트, 기타
  • 웹 기술: 스프링 MVC, 스프링 WebFlux
  • 데이터 접근 기술: 트랜잭션, JDBC, ORM 지원, XML 지원
  • 기술 통합: 캐시, 이메일, 원격 접근, 스케줄링
  • 테스트: 스프링 기반 테스트 지원
  • 언어: 코틀린, 그루비

스프링 부트

  • 스프링을 편리하게 사용할 수 있도록 지원, 최근에는 기본적으로 사용한다.
  • 단독으로 실행할 수 있는 스프링 애플리케이션을 쉽게 생성
  • Tomcat 같은 웹 서버를 내장해서 별도의 웹서버를 설치하지 않아도 된다.
  • 손쉬운 빌드 구성을 위한 starter 종속성 제공
  • 스프링과 서드 파티 라이브러리 자동 구성
  • 메트릭, 상태 확인, 외부 구성과 같은 프로덕션 준비 기능 제공
  • 관례에 의한 간결한 설정

앞에서 스프링의 핵심 개념은 좋은 객체 지향 애플리케이션 개발을 도와주는 프레임워크라고 배웠다.
그렇다면 좋은 객체 지향 프로그래밍이란 무엇일까?

 

먼저 객체 지향 프로그래밍이란 컴퓨터 프로그램을 단순히 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 객체들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다.(협력)
객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다.


객체 지향은 다음과 같은 특징을 갖고 있다.

  • 클래스와 객체 : 클래스는 데이터(속성)와 데이터를 처리하는 메서드를 포함하고 있으며 객체를 생성하기 위한 틀이다. 객체는 클래스의 인스턴스로 실제 데이터와 그 데이터를 다루는 메서드의 조합이다.
    클래스는 추상적인 개념이며 메모리를 차지하지 않고, 객체는 메모리에 할당되며 실제로 프로그램에 사용된다.
  • 캡슐화(Encapsulation) : 클래스의 내부 구현과 외부 사용을 분리하는 개념으로, 클래스 내부의 데이터와 메서드는 외부에서 직접 접근하지 못하도록 보호되고, 클래스 내부에서만 접근할 수 있도록 제한하는 것이다.
  • 상속(Inheritance): 기존 클래스의 특성과 동작을 다른 클래스가 확장하거나 재사용하는 개념이다. 부모 클래스의 특징을 자식 클래스가 물려받아 사용할 수 있으며, 이를 통해 코드의 재사용성과 유지보수성이 늘릴 수 있다.
  • 다형성(Polymorphism): 같은 메서드나 인터페이스를 다양한 형태로 구현하거나 확장하여 사용하는 능력을 의미한다. 
    즉, 서로 다른 두 클래스가 같은 메서드 이름을 가지고 있지만 각각 다른 동작을 수행할 수 있다는 의미이다.
  • 추상화(Abstraction): 추상화는 복잡한 현실 세계를 단순화하여, 필요한 부분만 표현하는 과정이다. 클래스나 인터페이스를 사용하여 공통된 특성을 추출하고, 이를 기반으로 실제 객체를 생성한다.

여기서 스프링의 핵심은 다형성이다. 다형성을 실세계로 비유하자면 세상을 역할과 구현으로 구분해 보자.
다음과 같은 그림이 있다.

만약 운전자가 아반떼를 팔고 K3를 산다면, 운전자는 면허를 새로 갱신해야 하는가?
대답은 "그럴 필요 없다."이다. 그저 바뀐 차를 운전하면 된다.

자동차가 바뀌어도 운전자에게는 영향을 주지 않아야 한다가 핵심이다.


1. 운전자(클라이언트)는 대상의 역할(인터페이스)만 알면 된다.
- 운전자는 자동차의 역할만 알면 된다.
2. 운전자(클라이언트)는 구현 대상의 내부 구조를 몰라도 된다.
- 운전자는 운전하는 법만 알면 되지 실제로 내부에서 어떻게 작동하는지 알 필요가 없다.
3. 운전자(클라이언트)는 구현 대상의 내부 구조가 변경되어도 영향을 받지 않는다.
4. 운전자(클라이언트)는 구현 대상 자체를 변경해도 영향을 받지 않는다.
- 차를 다른 차로 바꿔도 운전자는 새로운 차를 운전하면 그만이다.

또 하나의 다형성에 대한 예시가 있다.

로미오와 줄리엣 공연이 있다. 장동건과 김태희가 각각 로미오와 줄리엣 역할을 하기로 정해져 있다고 가정해 보자.
만약 김태희가 송혜교로 바뀐다면, 로미오 역할인 장동건도 영향을 받아야 하는가?
정답은 아니오이다. 배우는 누가 하더라도 대체가 가능해야 하며, 로미오는 줄리엣 역할을 누가 하던지 상관이 없고 오로지 나의 대본만 보면 역할 수행이 가능하다.

다른 예시로 키보드, 마우스, 정렬 알고리즘, 할인 정책 로직 등이 있다.
1. 새로 산 키보드나 마우스가 있다. 키보드나 마우스를 내가 새것으로 바꾼다고 해서 그게 컴퓨터에 어떠한 영향을 주는가?
2. 정렬 알고리즘이 있다. 예를 들어 버블 정렬로 구현한 로직이 들어간 애플리케이션이 있다고 가정해 보자. 버블 정렬에서 합병 정렬로 구현 로직을 변경하였다. 정렬 로직을 바꾼다고 해서 그 외 다른 로직이 들어간 부분에 영향이 있을까?


다형성은 다음과 같이 설명할 수 있다.

역할 - 인터페이스
구현 - 클래스, 구현 객체
객체를 설계 시 역할과 구현을 명확히 분리한다. 객체는 협력이라는 관계부터 생각하며 혼자 있는 객체는 없다.
역할(인터페이스)을 먼저 부여하고 그 역할을 수행하는 구현 객체를 만든다.
클라이언트는 요청하고, 서버는 응답한다. 수많은 객체 클라이언트와 객체 서버는 서로 협력 관계를 가진다고 할 수 있다.

다형성의 본질

 

  • 다형성의 본질을 이해하려면 협력이라는 객체사이의 관계에서 시작해야 한다.
  • 인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경할 수 있다.
  • 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다.

정리하자면 다형성을 통해 실세계의 역할구현이라는 콘셉트로 객체로 구현할 수 있다.
이렇게 설계하면 유연하고, 변경이 용이하며, 확장이 가능하도록 설계가 가능하다.

또한, 클라이언트에 영향을 주지 않는 변경이 가능하며, 역할(인터페이스)을 잘 설계하는 것이 중요하다.

스프링과 객체 지향

  • 다형성이 중요
  • 스프링은 다형성을 극대화해서 이용할 수 있게 도와준다.
  • 스프링에서 이야기하는 제어의 역전(IoC), 의존관계 주입(DI)은 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원한다.

스프링과 객체 지향 설계에 대해서 잘 알기 위해서는 한 가지를 더 알아야 하는데 바로 객체 지향 설계의 5원칙인 SOLID이다.

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

1. SRP-Single responsiblity principle(단일 책임 원칙)
- 한 클래스는 하나의 책임만 가져야 한다.
- 하나의 책임이라는 것은 모호하다고 볼 수 있으며, 중요한 기준은 변경으로 변경이 있을 때 파급 효과가 적으면
  단일 책임 원칙을 잘 따른 것이라고 볼 수 있다.
2. OCP-Open/closed principle(개방-폐쇄 원칙)
- 소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀있어야 한다.

- 다형성을 활용하여 역할과 구현의 분리를 생각해 보면 가능하다.
- 객체를 생성하고, 연관 관계를 맺어주는 별도의 조립, 설정자가 필요하며 그 역할을 스프링이 한다.
예시) 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현
3. LSP-Liskov substitution principle(리스코프 치환 원칙)
- 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.

- 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것. 다형성을 지원하기 위한 원칙으로 인터페이스를 구현한 구현체를 믿고 사용하려면 이 원칙이 필요하다.
예시) 자동차 인터페이스의 엑셀은 앞으로 가라는 기능을 의미한다. 만약 액셀을 밟았는데 뒤로 가게 구현한다면 이는 LSP를 위반한 것이다. 액셀을 밟으면 느리더라도 앞으로 가야 한다.
4. ISP-Interface segregation principle(인터페이스 분리 원칙)

- 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
- 분리하면 인터페이스가 명확해지고, 대체 가능성이 높아진다.
예시) 자동차 인터페이스 -> 운전 인터페이스, 정비 인터페이스로 분리한다. 분리하면 정비 인터페이스가 변해도 운전에는 영향을 주지 않는다.
5. DIP-Dependency inversion principle(의존관계 역전 원칙)
- 프로그래머는 "추상화에 의존해야지, 구체화에 의존하면 안 된다."
- 의존성 주입은 이 원칙을 따르는 방법 중 하나이다.

- 쉽게 이야기하자면 구현 클래스에 의존하지 않고, 인터페이스에 의존하라는 뜻이다.
- 다형성에서 이야기 한 역할에 의존하게 해야 한다는 것과 같다. 구현체에 의존하게 되면 변경이 어려워진다.

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

빈 생명주기 콜백  (0) 2023.09.08
컴포넌트 스캔  (0) 2023.09.08
싱글톤 패턴과 싱글톤 컨테이너  (0) 2023.09.08
스프링 핵심 원리 -2  (0) 2023.09.01
스프링 입문 요약  (0) 2023.08.14

본 내용은 해당 강의를 듣고 필요에 맞게 요약한 글입니다.

 

[무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., 스프링 학습 첫 길잡이! 개발 공부의 길을 잃지 않도록 도와드립니다. 📣 확인해주세

www.inflearn.com

 

앞으로 쓸 글들은 전체적인 리뷰보다는 내가 생각하기에 나에게 필요한 내용을 적으면서 진행하려고 합니다.

인텔리제이 단축키

자주 쓰는 단축키를 알려주셔서 나에겐 굉장히 유용했다.
- MacOS -

⌘ - Command(cmd)
⌃ - Ctrl
⌥ - Option(Alt)
⇧ - Shift
⇪ - Caps lock
Fn - Function

MacOS 윈도우(Windows) 기능
^ + space ctrl + space 자동 완성
⇧ + ⌘ + v ctrl + alt + v 변수 자동 완성
⌘ + n alt + insert Generate
(constructor,getter,setter,toString,test 등)
⇧ + F6 shift + f6 rename
^ + t ctrl + alt + shift + t 리팩토링

클래스 용어(네이밍)

서비스는 비즈니스적인 네이밍을 사용하고, 리포지토리는 기계적인 네이밍을 사용한다.


테스트코드(TestCode)

테스트 코드 작성 시에 테스트는 순서에 의존되게 작성하지 말 것.
순서와 상관없이 실행되어야 하며, 실행 시에 실행 순서를 보장하지 않음

@BeforeEach
@AfterEach

해당 어노테이션을 활용 하면 각각의 테스트 실행 전과 후에 코드를 실행시키는 작업을 수행할 수 있다.
ex)  필요한 데이터 초기화, 

 

테스트 코드 작성 시 assertj를 import하여 활용하면 다음과 같이 더욱 간결하게 코드 작성이 가능하다.

assertThat(member).isEqualTo(null);

테스트 코드에서는 예외 처리를 테스트하는 것도 굉장히 중요한데 예외처리 테스트코드를 작성하는 방법은 다음과 같다.

IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

assertThrows를 활용하여 예외 사항에 해당하는 예외를 던질 수 있다.
try-catch로 매번 테스트코드를 작성하는 것보다 효율적이다.

 

테스트코드 작성 시에는 메서드를 한글로 작성해도 큰 상관이 없다.

테스트 코드 작성시에는 Given-When-Then 구조로 작성을 하는 게 효율적이다.

1. Given : 테스트를 위해 필요한 조건이나 상황을 설정한다. 어떠한 상황이 주어졌을 때를 기술한다.

2. When : 테스트하려는 동작이나 행동을 나타낸다. 주어진 조건 아래에서 어떤 동작을 수행하는지 명시한다.
3. Then : 테스트의 결과나 예상되는 출력을 설명한다. When에서 수행한 동작에 대한 기대 결과를 나타낸다.

해당 강의에서는 다음과 같은 예시를 사용하였다.

@Test
void 회원가입() {
    // given
    Member member = new Member();
    member.setName("spring");

    // when
    Long saveId = memberService.join(member);

    // then
    Member findMember = memberService.findOne(saveId).get();
    assertThat(member.getName()).isEqualTo(findMember.getName());
}

1. spring이라는 이름을 가진 회원이 주어진다. (상황 설정)


2. 해당 회원은 서비스의 join이라는 메서드로 회원가입을 한다. (동작)
    회원 가입에 대한 결과로 아이디가 주어진다.

3. 주어진 아이디로 회원을 찾았을 때 이름이 일치하는지 확인한다. (기대 결과)

 

TDD(Test Driven Development)에서는 코드를 작성하기 전에 테스트 케이스를 먼저 작성한다.

 

스프링 빈을 등록하는 방법

스프링 컨테이너에 스프링 빈 등록 시 기본적으로 싱글톤으로 등록된다.
컴포넌트 스캔은 기본적으로 실행되는 메인 패키지의 하위 패키지를 스캔한다.

생성자에 @Autowired 가 있으면 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어준다. 
이렇게 객체 의존관계를 외부에서 넣어주는 것을 DI (Dependency Injection)의존성 주입이라 한다.

1. 컴포넌트 스캔, 자동 의존관계 설정
컴포넌트 스캔@Component 어노테이션이 있으면 스프링 빈에 자동으로 등록된다.

@Controller, @Service, @Repository 어노테이션이 컴포넌트가 아닌데
스프링 빈에 등록되는 이유는 해당 어노테이션을 눌러서 들어가보면
다음과 같이 @Component 어노테이션이 있는 걸 확인할 수 있다.

컴포넌트 스캔 예시 (회원 서비스)

@Service
public class MemberService {
    private final MemoryMemberRepository memberRepository;

    @Autowired
    public MemberService(MemoryMemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

 - 생성자에 @Autowired를 사용하면 객체 생성 시점에 스프링 컨테이너에서 해당 스프링 빈을 찾아서
주입한다. 

 - 생성자가 1개만 있으면 @Autowired는 생략할 수 있다.


2. 자바 코드로 직접 스프링 빈 등록

@Configuration 어노테이션을 선언한 클래스를 만들고, @Bean 어노테이션을 사용하여 스프링 빈을 등록한다. 예시는 다음과 같다.

@Configuration
public class SpringConfig {
    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository());
    }

    @Bean
    public MemoryMemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }
}

 

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

빈 생명주기 콜백  (0) 2023.09.08
컴포넌트 스캔  (0) 2023.09.08
싱글톤 패턴과 싱글톤 컨테이너  (0) 2023.09.08
스프링 핵심 원리 -2  (0) 2023.09.01
스프링 핵심 원리 요약 - 1  (0) 2023.08.20

riot 프로젝트를 진행하면서 카프카 클러스터를 구축했었는데
새로 산 맥북에서 기존의 환경을 구축하려고 했더니 쉽게 되지 않았다.
관련 내용을 해결했던 과정을 쓰고자 한다.

기존에 도커를 활용하여 카프카 클러스터링을 구축했었는데

기존에 구성했던 카프카 클러스터링의 docker-compose.yml은 다음과 같다.

version: '3.3'
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:latest
    ports:
    - "2181:2181"
    - "2888:2888"
    - "3888:3888"
    healthcheck:
      test: echo stat | nc localhost 2181
      interval: 10s
      timeout: 10s
      retries: 3
    environment:
    - ZOOKEEPER_SERVER_ID=1
    - ZOOKEEPER_CLIENT_PORT=2181
    - ZOOKEEPER_TICK_TIME=2000
    - ZOOKEEPER_INIT_LIMIT=5
    - ZOOKEEPER_SYNC_LIMIT=2
    - ZOOKEEPER_SERVERS=zookeeper:2888:3888
  kafka1:
    image: confluentinc/cp-kafka:latest
    healthcheck:
      test: ps augwwx | egrep [S]upportedKafka
    depends_on:
    - zookeeper
    ports:
    - "9091:9091"
    environment:
    - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9091
    - KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9091
    - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
    - KAFKA_BROKER_ID=1
    - BOOTSTRAP_SERVERS=kafka1:9091,kafka2:9092,kafka3:9093
    - ZOOKEEPER=zookeeper:2181
  kafka2:
    image: confluentinc/cp-kafka:latest
    healthcheck:
      test: ps augwwx | egrep [S]upportedKafka
    depends_on:
    - zookeeper
    ports:
    - "9092:9092"
    environment:
    - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092
    - KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092
    - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
    - KAFKA_BROKER_ID=2
    - BOOTSTRAP_SERVERS=kafka1:9091,kafka2:9092,kafka3:9093
    - ZOOKEEPER=zookeeper:2181
  kafka3:
    image: confluentinc/cp-kafka:latest
    healthcheck:
      test: ps augwwx | egrep [S]upportedKafka
    depends_on:
    - zookeeper
    ports:
    - "9093:9093"
    environment:
    - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9093
    - KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9093
    - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
    - KAFKA_BROKER_ID=3
    - BOOTSTRAP_SERVERS=kafka1:9091,kafka2:9092,kafka3:9093
    - ZOOKEEPER=zookeeper:2181

  kafdrop:
    image: obsidiandynamics/kafdrop
    restart: "no"
    ports: 
      - "9000:9000"
    environment:
      KAFKA_BROKER_CONNECT: "kafka1:9091"
    depends_on:
      - kafka1
      - kafka2
      - kafka3

실행해 보니 kafdrop은 mac os에서는 제대로 지원이 되지 않는 것 같았다.

그래서 먼저 kafdrop을 다른 걸로 대체하기로 하였다.


모니터링하기 위한 다른 ui 도구를 찾다가 해당 kafka-ui를 사용하기로 하였다.

https://hub.docker.com/r/provectuslabs/kafka-ui

 

Docker

 

hub.docker.com

kafka-ui카프카 클러스터링 관리를 위한 무료 오픈소스 웹 UI이다.

깃허브에는 자세한 사용 방법도 나와있다.

https://github.com/provectus/kafka-ui

 

 

kafka-ui:
  image: provectuslabs/kafka-ui
  ports:
    - "10000:8080"
  restart: always
  environment:
    - KAFKA_CLUSTERS_0_NAME=local
    - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka1:9091,kafka2:9092,kafka3:9093
    - KAFKA_CLUSTERS_0_ZOOKEEPER=zookeeper:2181

kafdrop을 제거하고 kafka-ui를 도커 컴포즈에 추가하였다. 다시 도커 컴포즈를 실행하였다.

kafka-ui

kafka-ui는 제대로 실행 됐지만 kafka와의 연결은 실패하였다.

관련 에러를 수정하기 위해 웹 서핑을 하다가 다른 내용을 봐버렸다.
카프카 클러스터링 시에 주키퍼도 클러스터의 안정성을 위해 3개 이상의 노드로 구성하는 것이 권장된다는 글을 보았다.
궁금해서 챗지피티에 검색해 보았는데 챗지피티의 답변은 다음과 같다.

주키퍼 앙상블을 3개 이상의 노드로 구성하는 것은 다음과 같은 이점을 가지게 됩니다:

높은 가용성: 주키퍼 앙상블 내에서 한 노드의 장애가 발생해도 다른 노드가 여전히 서비스를 제공하므로 전체 시스템의 가용성이 유지됩니다.

분산 투표 시스템: 주키퍼는 분산 투표 알고리즘을 사용하여 결정을 내립니다. 이때 홀수 개의 노드가 필요한데, 3개 이상의 노드로 구성하면 다수결을 쉽게 결정할 수 있습니다.

분산 데이터 저장: 주키퍼는 분산 데이터베이스처럼 작동하여 클라이언트의 상태 정보를 저장하고 유지합니다. 노드가 추가되거나 제거될 때에도 데이터의 일관성과 안정성을 유지할 수 있습니다.

일반적으로 카프카 클러스터 내의 주키퍼 앙상블은 3개 혹은 5개 노드로 구성하는 것이 일반적인 모범 사례입니다.

다음과 같은 답변을 받아서 우선 주키퍼를 3개의 노드로 구성하기로 하였다.

zookeeper-1:
  image: confluentinc/cp-zookeeper:latest
  hostname: zookeeper-1
  ports:
    - "12181:12181"
  environment:
    ZOOKEEPER_SERVER_ID: 1
    ZOOKEEPER_CLIENT_PORT: 12181
    ZOOKEEPER_TICK_TIME: 2000
    ZOOKEEPER_INIT_LIMIT: 5
    ZOOKEEPER_SYNC_LIMIT: 2
    ZOOKEEPER_SERVERS: zookeeper-1:12888:13888;zookeeper-2:22888:23888;zookeeper-3:32888:33888

zookeeper-2:
  image: confluentinc/cp-zookeeper:latest
  hostname: zookeeper-2
  ports:
    - "22181:22181"
  environment:
    ZOOKEEPER_SERVER_ID: 2
    ZOOKEEPER_CLIENT_PORT: 22181
    ZOOKEEPER_TICK_TIME: 2000
    ZOOKEEPER_INIT_LIMIT: 5
    ZOOKEEPER_SYNC_LIMIT: 2
    ZOOKEEPER_SERVERS: zookeeper-1:12888:13888;zookeeper-2:22888:23888;zookeeper-3:32888:33888

zookeeper-3:
  image: confluentinc/cp-zookeeper:latest
  hostname: zookeeper-3
  ports:
    - "32181:32181"
  environment:
    ZOOKEEPER_SERVER_ID: 3
    ZOOKEEPER_CLIENT_PORT: 32181
    ZOOKEEPER_TICK_TIME: 2000
    ZOOKEEPER_INIT_LIMIT: 5
    ZOOKEEPER_SYNC_LIMIT: 2
    ZOOKEEPER_SERVERS: zookeeper-1:12888:13888;zookeeper-2:22888:23888;zookeeper-3:32888:33888

다음과 같이 구성하였다.

실행하였더니 정상적으로 컨테이너 실행은 되었으나 여전히 다음과 같이 호스트 머신과 카프카와의 연결이 되지 않았다.

또한 로컬 환경의 인텔리제이에서 카프카와의 연결을 구현한 스프링 부트 모듈을 실행을 해봤을 때도 다음과 같은 타임아웃 에러가 발생했다.

https://github.com/hamwoojo/riot-api

java.util.concurrent.TimeoutException: null
	at java.base/java.util.concurrent.CompletableFuture.timedGet(CompletableFuture.java:1960) ~[na:na]
	at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2095) ~[na:na]
	at org.apache.kafka.common.internals.KafkaFutureImpl.get(KafkaFutureImpl.java:180) ~[kafka-clients-3.1.2.jar:na]
	at org.springframework.kafka.core.KafkaAdmin.initialize(KafkaAdmin.java:214) ~[spring-kafka-3.0.4.jar:3.0.4]
	at org.springframework.kafka.core.KafkaAdmin.afterSingletonsInstantiated(KafkaAdmin.java:183) ~[spring-kafka-3.0.4.jar:3.0.4]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:974) ~[spring-beans-5.3.26.jar:5.3.26]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:920) ~[spring-context-5.3.26.jar:5.3.26]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.26.jar:5.3.26]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.10.jar:2.7.10]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) ~[spring-boot-2.7.10.jar:2.7.10]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.10.jar:2.7.10]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) ~[spring-boot-2.7.10.jar:2.7.10]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) ~[spring-boot-2.7.10.jar:2.7.10]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) ~[spring-boot-2.7.10.jar:2.7.10]
	at riot.api.data.engineer.DataEngineerApplication.main(DataEngineerApplication.java:10) ~[classes/:na]

해당 원인은 다음과 같았다.

결론부터 말하면, 호스트 머신과 컨테이너 간의 통신을 하기 위해서는 localhost가 아니라 host.docker.internal 도메인을 사용해야 한다.


host.docker.internal은 macOS 환경에서 Docker 컨테이너 내부에서 호스트 머신을 가리키는 도메인 이름이다.
macOS에서 Docker를 사용할 때, 이 도메인을 사용하여 컨테이너 내부와 호스트 머신 간의 통신을 수행할 수 있다.

 

최종적으로 수정한 docker-compose.yml 파일은 다음과 같다.

version: '3.7'
services:
  zookeeper-1:
    image: confluentinc/cp-zookeeper:latest
    hostname: zookeeper-1
    ports:
      - "12181:12181"
    environment:
      ZOOKEEPER_SERVER_ID: 1
      ZOOKEEPER_CLIENT_PORT: 12181
      ZOOKEEPER_TICK_TIME: 2000
      ZOOKEEPER_INIT_LIMIT: 5
      ZOOKEEPER_SYNC_LIMIT: 2
      ZOOKEEPER_SERVERS: zookeeper-1:12888:13888;zookeeper-2:22888:23888;zookeeper-3:32888:33888

  zookeeper-2:
    image: confluentinc/cp-zookeeper:latest
    hostname: zookeeper-2
    ports:
      - "22181:22181"
    environment:
      ZOOKEEPER_SERVER_ID: 2
      ZOOKEEPER_CLIENT_PORT: 22181
      ZOOKEEPER_TICK_TIME: 2000
      ZOOKEEPER_INIT_LIMIT: 5
      ZOOKEEPER_SYNC_LIMIT: 2
      ZOOKEEPER_SERVERS: zookeeper-1:12888:13888;zookeeper-2:22888:23888;zookeeper-3:32888:33888

  zookeeper-3:
    image: confluentinc/cp-zookeeper:latest
    hostname: zookeeper-3
    ports:
      - "32181:32181"
    environment:
      ZOOKEEPER_SERVER_ID: 3
      ZOOKEEPER_CLIENT_PORT: 32181
      ZOOKEEPER_TICK_TIME: 2000
      ZOOKEEPER_INIT_LIMIT: 5
      ZOOKEEPER_SYNC_LIMIT: 2
      ZOOKEEPER_SERVERS: zookeeper-1:12888:13888;zookeeper-2:22888:23888;zookeeper-3:32888:33888

  kafka1:
    image: confluentinc/cp-kafka:latest
    healthcheck:
      test: ps augwwx | egrep [S]upportedKafka
    depends_on:
    - zookeeper-1
    - zookeeper-2
    - zookeeper-3
    ports:
    - "9091:9091"
    environment:
    - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://host.docker.internal:9091
    - KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9091
    - KAFKA_ZOOKEEPER_CONNECT=zookeeper-1:12181,zookeeper-2:22181,zookeeper-3:32181
    - KAFKA_BROKER_ID=1
    - BOOTSTRAP_SERVERS=host.docker.internal:9091,host.docker.internal:9092,host.docker.internal:9093
    - ZOOKEEPER=zookeeper-1:12181
  kafka2:
    image: confluentinc/cp-kafka:latest
    healthcheck:
      test: ps augwwx | egrep [S]upportedKafka
    depends_on:
      - zookeeper-1
      - zookeeper-2
      - zookeeper-3
    ports:
    - "9092:9092"
    environment:
    - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://host.docker.internal:9092
    - KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092
    - KAFKA_ZOOKEEPER_CONNECT=zookeeper-1:12181,zookeeper-2:22181,zookeeper-3:32181
    - KAFKA_BROKER_ID=2
    - BOOTSTRAP_SERVERS=host.docker.internal:9091,host.docker.internal:9092,host.docker.internal:9093
    - ZOOKEEPER=zookeeper-1:12181,zookeeper-2:22181,zookeeper-3:32181
  kafka3:
    image: confluentinc/cp-kafka:latest
    healthcheck:
      test: ps augwwx | egrep [S]upportedKafka
    depends_on:
      - zookeeper-1
      - zookeeper-2
      - zookeeper-3
    ports:
    - "9093:9093"
    environment:
    - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://host.docker.internal:9093
    - KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9093
    - KAFKA_ZOOKEEPER_CONNECT=zookeeper-1:12181,zookeeper-2:22181,zookeeper-3:32181
    - KAFKA_BROKER_ID=3
    - BOOTSTRAP_SERVERS=kafka1:9091,kafka2:9092,kafka3:9093
    - ZOOKEEPER=zookeeper-1:12181,zookeeper-2:22181,zookeeper-3:32181

  kafka-ui:
    image: provectuslabs/kafka-ui
    container_name: kafka-ui
    ports:
      - "10000:8080"
    restart: always
    environment:
      - KAFKA_CLUSTERS_0_NAME=local
      - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=host.docker.internal:9091,host.docker.internal:9092,host.docker.internal:9093
      - KAFKA_CLUSTERS_0_ZOOKEEPER=zookeeper-1:12181,zookeeper-2:22181,zookeeper-3:32181

localhost를 host.docker.internal로 수정하였다.
그리고 호스트 머신의 ip를 찾아야 하는데 해당 명령어는 다음과 같다.

ifconfig | grep "inet "

ip를 찾았으면 hosts 파일에 등록한다.

sudo vi /etc/hosts
{ip} host.docker.internal

해당 내용을 제일 마지막 줄에 추가해 준다.

다시 도커 컴포즈를 실행해 보자.

kafka-ui

kakfa-ui를 확인해 보니 제대로 연결된 걸 확인했다. 인텔리제이에서 스프링 부트 모듈을 실행해 보자.

해당 모듈에는 topic이 자동으로 생성되게 해 놓았는데, 정상적으로 실행이 완료된다면 5개의 토픽이 생길 것이다.

kafka-ui

정상적으로 토픽이 생성된 걸 확인할 수 있다.

마지막으로 토픽에 제대로 메시지가 들어오는지 확인하기 위해서

리그 오브 레전드 게임의 item 정보를 가져오는 api를 호출하였다.

아이템 정보가 잘 가져와지는 걸 확인했다. 얼마 안 걸릴 줄 알았는데 구축하는데 생각보다 오랜 시간이 걸렸다. 
아무래도 mac을 처음 써봐서 mac 관련 단축키나 기능을 배우는 데도 시간이 걸려서 그랬던 것 같다.
다음엔 해당 프로젝트에 스웨거(Swagger)를 붙이거나, 아니면 Logstash와 ElasticSearch를 macOS에 구축해보려고 한다.

+ Recent posts