Today
-
Yesterday
-
Total
-
  • 스프링 부트 핵심 가이드 1주차 : 개발 준비 학습
    책 공부/스프링 부트 핵심 가이드 2023. 12. 2. 00:01

    📖 책 정보

    날 위한 모바일 가이드

    01장 스프링 부트란? 1. 스프링 프레임워크 2. 스프링 vs 스프링 부트

    02장 개발에 앞서 알면 좋은 기초 지식 1. 서버 간 통신 2. 스프링 부트의 동작 방식 3. 레이어드 아키텍처 4. 디자인 패턴 5. REST API


    01장 스프링 부트란?

    1. 스프링 프레임워크
    2. 스프링 vs 스프링 부트

    키워드

    스프링 엔터프라이즈급 개발 스프링 핵심 가치 IoC DI AOP aspect OOP 핵심기능 부가기능 스프링 부트 의존성 관리 Auto Configuration @Component bean @Component 시리즈 어노테이션 대표 5개 내장 WAS 모니터링



    1. 스프링 프레임워크

    스프링 프레임워크는

    자바 기반의 애플리케이션 프레임워크로 ,

    엔터프라이즈급 애플리케이션을 개발하기 위한 다양한 기능을 제공한다.

    ✏️ 엔터프라이즈급 애플리케이션 개발 ?
    
    기업 환경을 대상으로 하는 개발.
    대규모 데이터를 처리하는 환경을 엔터프라이즈 환경이라고 한다.
    

    쉽게 말해

    자바로 애플리케이션을 개발하는 데 필요한 기능을 제공하고,

    쉽게 사용하도록 돕는 도구이다.

    스프링의 핵심 가치

    애플리케이션 개발에 필요한 기반을 제공하여 개발자가 비즈니스 로직에 집중하게 하는 것!


    스프링의 특징과 구조

    - 1. IoC

    제어의 역전 Inversion of Control

    객체의 관리를 컨테이너( 스프링 컨테이너 , IoC 컨테이너 )에 맡겨

    제어권이 개발자에서 프레임워크로 넘어간 것을

    제어의 역전이라고 한다.


    제어의 역전을 통해

    의존성 주입 DI ,

    관점 지향 프로그래밍 AOP 등이 가능해진다.


    제어의 역전으로 인해

    개발자가 객체를 관리해야 하는 비용을 줄일 수 있게 됨으로,

    비즈니스 로직을 작성하는데에 더 집중할 수 있게된다.


    - 2. DI

    의존성 주입 Dependency Injection

    제어 역전의 방법 중 하나이다.

    사용 할 객체를 개발자가 직접 생성하지 않고 외부 컨테이너 (스프링 컨테이너, IoC컨테이너)가 생성한 객체를 주입받아 사용하는 방식을 의미한다.

    스프링의 의존성을 주입받는 방법은 세 가지가 있다.

    1. 생성자를 통한 의존성 주입
    2. 필드 객체 선언을 통한 의존성 주입
    3. setter 메서드를 통한 의존성 주입

    이 중 스프링 공식문서에서는 생성자를 통한 의존성 주입 방식을 권장하고 있다.

    생성자를 통해 의존성을 주입받게 되면 레퍼런스 객체 없이는 객체를 초기화 할 수 없도록 설계할 수 있기 때문이다.

    🐢 개인적으로 이 말이 좀 어려워서 chat봇에게 도움을 받아보았다.
    
    생성자를 통해 의존성을 주입하는 방식은 
    해당 객체가 필요한 의존성 없이는 생성될 수 없도록 강제하는 것입니다. 
    이는 해당 객체가 올바른 상태로 초기화될 수 있도록 보장하며, 
    해당 객체를 사용하는 다른 코드에게도 해당 객체가 올바른 상태로 초기화되었다는 것을 
    보장합니다.
    

    - 3.  AOP 

    관점 지향 프로그래밍 Aspect Oriented Programming

    스프링의 아주 중요한 특징이다.

    관점 지향 프로그래밍을 이해하면 객체 지향 프로그래밍을 더욱 잘 사용할 수 있다.

    ✏️ 객체 지향 프로그래밍 : OOP : Object Oriented Programming
    
    	> 각 기능을 재사용 가능한 개별 객체로 구성해 프로그래밍 하는 것
    	> 추상화 , 캡슐화 , 상속 , 다형성
    

    AOP는 관점 Aspect을 기준으로 묶어 개발하는 방식을 의미한다.

    여기서 관점이란,

    어떤 기능을 구현할 때 기능을 핵심기능과 부가기능으로 구분해

    각각을 하나의 관점으로 보는 것을 의미한다.

    1. 핵심 기능

    비즈니스 로직을 구현하는 과정에서 비즈니스 로직이 처리하려는 목적 기능을 말함

    2. 부가 기능

    핵심 기능이 어떤 기능인지와 무관하게 로직이 수행되기 전 , 후에 수행되기만 하면 되는 기능을 말함


    예를 들어,

    클라이언트로부터 상품 정보 등록 요청을 받아 데이터베이스에 저장하고, 그 상품 정보를 조회하는 비즈니스 로직을 구현한다면

    상품 정보를 데이터베이스에 저장하고, 저장된 상품 정보 데이터를 보여주는 코드가 핵심 기능이다.

    그리고 이러한 비즈니스 로직. 핵심 기능 사이에 로깅 처리를 하거나 , 트랜잭션을 처리하는 코드를 넣어야 하는 상황이 발생하게 되는데 이러한 상황을 말하는 기능이 부가 기능이다.


    이처럼 여러 비즈니스 로직에서 반복되는 부가 기능을 하나의 공통 로직으로 처리하도록 모듈화해 삽입하는 방식AOP라고 한다.

    반복되는 부가기능을 모듈화해서 객체로 사용하게 되면 재사용이 가능해지고, 사용하기도 수월하기 때문에 개발자가 비즈니스 로직을 구현하는데에 좀 더 집중할 수 있게 된다.


    AOP 구현 방법은

    다음과 같이 크게 3가지로 나누어 볼 수 있는데, 이 중에서 스프링은 프락시 패턴을 통해 AOP 기능을 제공한다.

    1. 컴파일 과정에 삽입하는 방식
    2. 바이트코드를 메모리에 로드하는 과정에 삽입하는 방식
    3. 프락시 패턴을 이용한 방식

    - 4. 스프링 프레임워크의 다양한 모듈

    스프링 프레임워크는 기능별로 구분 된 약 20여개의 모듈로 구성돼 있다.

    이것은 선택지가 넓다는 의미이고,

    이 모든 모듈을 사용 할 필요는 없으며,

    스프링은

    개발에 필요한 모듈만 선택해서 사용하게끔 설계되어있는

    경량 컨테이너 구조의 프레임워크이다.



        _____________________🐢🎈____________________


    2. 스프링 VS 스프링 부트

    스프링에 필요한 모듈을 추가하다 보면

    설정이 복잡해지는 문제를 직면하게 되는데

    이를 해결하기 위해 등장한 것이 스프링 부트이다.


    즉, 별도의 복잡한 설정을 하지 않아도 되는 스프링 부트를 사용하면 개발이 쉬워진다.


    다음으로 스프링 프레임워크와 비교했을 때 스프링 부트가 가진 특징 4가지를 알아본다.

    - 1. 의존성 관리

    스프링 프레임워크에서는 개발에 필요한 각 모듈의 의존성을 직접 설정했어야 했고, 호환되는 버전을 찾아 입력해주어야 정상 동작 했다.

    하지만 스프링 부트는 이 모든 것을 지원해준다.

    spring-boot-starter로 시작하는 의존성을 제공하여 각 라이브러리의 기능과 관련해서 자주 사용되고, 서로 호환되는 버전의 모듈 조합을 제공해준다.

    이를 통해 라이브러리 호환 문제를 스프링 부트가 스스로 해결해준다.


    - 2. 자동 설정

    스프링 부트는 스프링 프레임워크의 기능을 사용하기 위한 자동 설정 Auto Configuration을 지원한다.

    자동 설정은 애플리케이션에 추가된 라이브러리를 실행하는 데 필요한 환경 설정을 알아서 찾아준다.

    즉 , 애플리케이션을 개발하는 데 필요한 의존성을 추가하면 프레임워크가 자동으로 관리해준다.


    예를 들면 스프링 부트 프로젝트에서 실행이 시작되는 main 메서드를 품은 클래스파일을 열어봤을 때 @SpringBootApplication 이라는 어노테이션이 붙어있는 것을 확인할 수 있다.

    이 어노테이션을 열어보면 다양한 기능을 하는 어노테이션들이 속해있는데, 이 중에서 프로그램 구성에 영향을 주는 어노테이션은 3개이다.

    @SpringBootConfiguration

    @EnableAutoConfiguration

    spring-boot-autoconfigure 패키지 안에 spring.factories 파일을 추가해 다양한 자동 설정이 일부 조건(각 파일에 설정된 @Conditional 조건)을 충족할 경우 빈에 등록되고 애플리케이션에 자동 반영된다.

    더 심오하게 알아보고 싶다면 : donghyeon.dev

    @ComponentScan

    스프링 부트 애플리케이션이 실행되면 @ComponentScan 어노테이션이 붙은 클래스를 발견해 빈 bean을 등록한다.

    ✏️ @ComponentScan 어노테이션이 붙은 클래스
    
    @Controller
    @RestController
    
    @Service
    
    @Repository
    
    @Configuration
    

    - 3. 내장 WAS

    스프링 부트의 각 웹 애플리케이션에는 내장 WAS( Web Application Server )가 존재한다.

    스프링 부트의 자동 설정 기능은 톰캣에도 적용이 되므로 특별한 설정을 해주지 않아도 톰캣이 실행된다.

    필요에 따라 톰캣을 다른 웹서버로 대체할 수도 있다.


    - 4. 모니터링

    개발이 끝나고 서비스를 운영하는 시기에는

    해당 시스템이 사용하는 스레드 , 메모리 , 세션 등의 주요 요소들을 모니터링 해야 한다.

    이를 위해 스프링 부트는 스프링 부트 액추에이터라는 자체 모니터링 도구를 지원한다.

    스프링 부트 액추에이터 라이브러리를 사용하려면 의존성(dependency)을 추가해주어야 하며,

    이 부분에 대해서는 마지막 주 쯤 다뤄보도록 한다!



        _____________________🐢🎈____________________


    02장 개발에 앞서 알면 좋은 기초 지식

    애플리케이션이 어떻게 동작하는지 , 왜 이렇게 구성되는지를 알기 위한 지식 학습

    • 서버 간 통신
    • 스프링 부트의 동작 방식
    • 레이어드 아키텍처
    • 디자인 패턴
    • REST API

    키워드 MSA 마이크로 서비스 아키텍처 서버 간 통신 서블릿 서블릿 컨테이너 톰캣 Dispatcher Servlet 핸들러 매핑 뷰 리졸버 MessageConverter 레이어드 아키텍처 프레젠테이션 계층 비즈니스 계층 데이터 접근 계층 디자인 패턴 생성 패턴 구조 패턴 행위 패턴 REST API REST API 유니폼 인터페이스 무상태성 캐시 가능성 레이어 시스템 클라이언트-서버 아키텍처 REST URI 설계 규칙


    1. 서버 간 통신

    마이크로서비스 아키텍처 MSA : Microservice Architecture

    서비스 규모를 작게 나누어 구성한 아키텍처.


    예를 들어 포털사이트를 MSA 로 개발한다면

    포털사이트라는 주제안에 포함 된 기능을

    하나 하나 전부 쪼개서

    블로그 , 카페 , 메일 , 뉴스 등 각각의 독립적인 프로젝트로 나누어 개발할 수 있다.


    마이크로서비스 아키텍처 MSA 방식으로 개발을 한다면

    각 서비스 간에 통신해야 하는 경우가 발생한다.

    이런 상황에서의 통신을 서버 간 통신이라고 한다.


    서버 간 통신

    한 서버가 다른 서버에 통신을 요청하는 것을 의미한다.

    이렇게 되면 한 대는 서버, 다른 한 대는 클라이언트가 된다.


    이러한 통신은 프로토콜을 통해 이루어지므로

    다양한 통신 방식이 있는데

    가장 많이 사용되는 방식은 HTTP / HTTPS 방식이다.



        _____________________🐢🎈____________________


    2. 스프링 부트의 동작 방식

    스프링 부트에서 spring-boot-starter-web 모듈을 사용하면

    기본적으로 톰캣을 사용하는 스프링 MVC 구조를 기반으로 동작한다.


    일반적인 웹 요청이 들어오면 스프링 부트는 아래와 같이 동작한다.

    Dispatcher Servlet 은 스프링에서 서블릿 역할을 한다.

    ✏️ 서블릿 Servlet

    클라이언트의 요청을 처리하고 결과를 반환하는 자바 웹 프로그래밍 기술.
    
    서블릿은 서블릿 컨테이너에서 관리한다.
    
    서블릿 컨테이너는 서블릿 인스턴스를 생성하고 관리하는 역할을 수행하는 주체이다.
    
    서블릿 얘기할 떄 톰캣을 빼놓을 수 없는데,
    
    톰캣은 
    WAS Web Application Server 의 역할과 
    서블릿 컨테이너의 역할을 수행하는
    대표적인 컨테이너이다.
    
    서블릿 컨테이너 특징
    1. 서블릿 객체를 생성, 초기화, 호출, 종료하는 생명주기를 관리한다.
    2. 서블릿 객체는 싱글톤 패턴으로 관리한다.
    3. 멀티 스레딩을 지원한다.
    

    일반적으로 스프링은 톰캣을 임베드해서 사용한다.

    그렇기 때문에 서블릿 컨테이너(embed 톰캣 포함)와 Dispatcher Servlet은 자동 설정 된 web.xml의 설정값을 공유한다.

    web.xml 파일을 못본것 같아서 구글링 해보았더니
    "자동 설정 된 web.xml"로 읽었어야 했다.
    
    web.xml 파일은 
    Servlet 3.0부터 없어지는 추세였다가 지금은 완전히 사라졌고, 
    annotation과 자바코드로 대체 되었다고 한다.
    
    Spring Boot에서는 
    
    어플리케이션 설정과 관련해서는 
    application.properties 또는 application.yml 파일을 작성하여 설정할 수 있고,
    
    웹 관련 설정과 관련해서는
    @Configuration 어노테이션을 가진 Java 클래스를 통해 설정 정보를 정의하며, 
    이를 통해 Servlet 등록, 필터 등록, 인터셉터 등록 등 웹 관련 설정을 할 수 있다.
    
    - 일부 ChatGPT 발췌
    

    Dispatcher Servlet의 동작을 간략히 살펴본다면,

    1. Dispatcher Servlet으로 요청이 들어왔을 때

    Dispatcher Servlet은 핸들러 매핑을 통해 요청 URI에 매핑된 핸들러를 탐색한다. -> 여기서 핸들러 = 컨트롤러

    ✏️ 핸들러 매핑 Handler Mapping
    
    - 요청 정보를 기준으로 어떤 컨트롤러를 사용할지 선정하는 인터페이스
    - 여러 구현체를 가지며, 대표적인 구현체 클래스는 다음과 같다.
    	- BeanNameUrlHandlerMapping
          : bean 이름을 url로 사용하는 매핑 전략
            bean을 정의할 때 / 가 들어가면 매핑 대상이 된다.
    	- ControllerClassNameHandlerMapping
          : url과 일치하는 클래스 이름을 갖는 bean을 컨트롤러로 사용하는 전략
            이름 중 controller를 제외한 앞부분만 소문자로 매핑한다.
    	- SimpleUrlHandlerMapping
          : url 패턴에 매핑된 컨트롤러를 사용하는 전략
        - DefaultAnnotationMapping
          : 어노테이션으로 url과 컨트롤러를 매핑하는 방법
    

    2. 핸들러 어댑터로 컨트롤러를 호출한다.

    3. 핸들러 어댑터에 컨트롤러의 응답이 돌아오면 ModelAndView로 응답을 가공해 반환한다.

    ModelAndView 
    - SpringMVC에서 사용하는 클래스.
    - 컨트롤러에서 뷰로 데이터를 전달하고, 어떤 뷰를 사용할 것인지를 결정하는데 사용된다.
    

    4-1. 뷰 형식으로 리턴하는 컨트롤러를 사용할 때는 뷰 리졸버를 통해 뷰를 받아 리턴한다.

    View Resolver
    - Spring MVC 프레임워크에서 사용되는 인터페이스. 
    - 이 인터페이스는 뷰 이름(String)을 실제 뷰 객체로 변환해준다.
    

    4-2. 뷰가 없이 JSON 형태의 리턴을 하는 REST 컨트롤러를 사용할 때는 MessageConverter를 통해 응답한다.

    MessageConverter
    - 요청과 응답에 대해 Body 값을 변환하는 역할을 수행
    - 스프링 부트에서는 HttpMessageConverter 인터페이스를 사용한다.
      HttpMessageConverter 인터페이스는 
      Content-type을 참고해서 Converter를 선정하는데,
      스프링 부트에서는 자동으로 설정되기 때문에 별도의 설정이 필요하지 않다.
    



        _____________________🐢🎈____________________


    3. 레이어드 아키텍처

    애플리케이션의 컴포넌트를

    유사 관심사를 기준으로

    레이어로 묶어 수평적으로 구성한 구조를 의미한다.


    어떻게 설계하느냐에 따라 용어와 계층의 수가 달라지지만

    일반적으로 3계층 또는 4계층 구성을 사용한다.

    여기서는 3계층으로 이루어진 레이어드 아키텍처를 살펴본다.


    하나의 애플리케이션에서 적용된 레이어드 아키텍처 3계층

    - 1. 프레젠테이션 계층

    • 애플리케이션의 최상단 계층으로, 클라이언트의 요청을 해석하고 응답하는 역할을 한다.

    • UI나 API를 제공한다.

    • 비즈니스 로직을 포함하지 않는다.

    • 비즈니스 계층으로 요청을 위임하고, 받은 결과를 응답하는 역할을 수행한다.

    - 2. 비즈니스 계층

    • 애플리케이션이 제공하는 기능을 정의.
    • 세부 작업을 수행하는 도메인 객체를 통해 업무를 위임하는 역할을 수행
    • DDD Domain Driven Design 기반의 아키텍처에서는 비즈니스 로직에 도메인이 포함되기도 하고, 별도로 도메인 계층을 두기도 한다.

    - 3. 데이터 접근 계층

    • 데이터베이스에 접근하는 일련의 작업을 수행한다.

    레이어드 아키텍처 기반 애플리케이션 설계 (application 여러개)

    • 각 레이어는 가장 가까운 하위 레이어의 의존성을 주입받는다.

    • 각 레이어는 관심사에 따라 묶여있으며, 다른 레이어의 역할침범하지 않는다.

      • 각 컴포넌트의 역할이 명확하므로 , 코드의 가독성과 기능 구현에 유리.
      • 코드의 확장성이 좋아짐
    • 각 레이어가 독립적으로 작성되면 다른 레이어와의 의존성을 낮춰 단위 테스트에 용이하다.


    Spring MVC 의 레이어드 아키텍처

    spring mvc는 Model View Controller 구조로,

    ViewControlelr는 프레젠테이션 계층 영역이며, Model은 비즈니스와 데이터 접근 계층의 영역으로 구분할 수 있다.

    그렇기 때문에 spring mvc 모델로 레이어드 아키텍처를 구현하려면 역할을 더 작게 쪼개야 한다.

    비즈니스 계층에 서비스를 배치해 엔티티와 같은 도메인 객체의 비즈니스 로직을 조합하도록 하고,

    데이터 접근 계층에는 DAO ( JPA 사용시 Repository ) 를 배치해 도메인을 관리한다.

    프레젠테이션 계층

    • 상황에 따라 유저 인터페이스 계층이라고도 한다.

    • 클라이언트로부터 데이터와 함께 요청받고 처리 결과를 응답으로 전달하는 역할

    비즈니스 계층

    • 상황에 따라 서비스 계층이라고도 한다.

    • 핵심 비즈니스 로직을 구현하는 영역이다.

    • 트랜잭션 처리나 유효성 검사 등의 작업도 수행한다.

    데이터 접근 계층

    • 상황에 따라 영속 Persistence 계층이라고도 한다.

    • 데이터베이스에 접근해야 하는 작업을 수행한다.

    • DAO 또는 Repository ( JPA 사용시 ).



        _____________________🐢🎈____________________


    4. 디자인 패턴

    소프트웨어 설계시 자주 발생하는 문제들을 해결하기 위해 고안된 해결책.

    유사한 문제가 많고 동일한 해결책을 적용하는 경우가 많아 패턴이라는 단어가 붙었다.

    하지만, 디자인 패턴이 모든 문제의 정답은 아니며 상황에 맞는 최적 패턴을 결정해서 사용하는 것이 바람직하다.


    디자인 패턴을 구체화해서 정리한 대표적인 분류 방식으로

    GoF 디자인 패턴이 있다.

    여기서 GoF는 Gang of Four의 줄임말로, 이 패턴을 체계화해서 분류한 4명의 인물을 의미한다.

    GoF 디자인 패턴은 다음의 3가지로 구분한다.

    +++ 추가해야됨 🐢

    - 1. 생성 패턴

    객체 생성에 사용되는 패턴. 객체를 수정해도 호출부가 영향을 받지 않게 한다.

    속하는 패턴 이름

    추상 팩토리 Abstract Factory

    빌더 Builder

    팩토리 메서드 Factory Method

    프로토타입 Prototype

    싱글톤 Singleton


    - 2. 구조 패턴

    객체를 조합해서 더 큰 구조를 만드는 패턴.

    속하는 패턴 이름

    어댑터 Adapter

    브리지 Bridge

    컴포지트 Composite

    데코레이터 Decorator

    퍼사드 Facade

    플라이웨이트 Flyweight

    프락시 Proxy


    - 3. 행위 패턴

    객체 간의 알고리즘이나 책임 분배에 관한 패턴. 객체 하나로는 수행할 수 없는 작업을 여러 객체를 이용해 작업을 분배한다. 결합도 최소화를 고려할 필요가 있다.

    속하는 패턴 이름

    책임 연쇄 Chain of Responsibility

    커맨드 Command

    인터프리터 Interpreter

    이터레이터 Iterator

    미디에이터 Mediator

    메멘도 Memento

    옵저버 Observer

    스테이트 State

    스트레이티지 Strategy

    템플릿 메서드 Template Method

    비지터 Visitor



        _____________________🐢🎈____________________


    5. REST API

    대중적으로 가장 많이 사용되는 애플리케이션 인터페이스.

    이 인터페이스를 통해 클라이언트가 서버에 접근하고 자원을 조작할 수 있다.

    - 1. REST란?

    Representational State Transfer 의 약자.

    WWW와 같은 분산 하이퍼미디어 시스템 아키텍처의 한 형식이다.

    주고 받는 Resource에 이름을 규정하고 URI에 명시하여

    HTTP 메서드를 통해 해당 자원의 상태를 주고받는 것을 의미한다.


    - 2. REST API란?

    API Application Programming Interface 는 애플리케이션에서 제공하는 인터페이스를 의미한다.

    API를 통해 서버 또는 프로그램 사이를 연결 할 수 있다.

    즉, REST API는 REST 아키텍처를 따르는 시스템/애플리케이션 인터페이스 라고 볼 수 있다.

    REST 아키텍처를 구현하는 웹 서비스를 RESTful 하다 라고 표현한다.


    - 3. REST의 특징

    유니폼 인터페이스

    일관된 인터페이스를 의미한다.

    프로그래밍 언어와 상관 없이, 플랫폼 및 기술에 종속되지 않고 타 언어, 플랫폼, 기술 등과 호환해 사용할 수 있음을 의미한다.

    무상태성 stateless

    서버에 상태 정보를 따로 보관하거나 관리하지 않음

    캐시 가능성

    REST는 HTTP 표준을 그대로 사용하므로

    HTTP의 캐싱 기능을 적용할 수 있다.

    이 기능을 사용하면 서버의 트랜잭션 부하가 줄어 효율적이며

    사용자 입장에서 성능이 개선된다.

    레이어 시스템

    REST 서버는 네트워크 상의 여러 계층으로 구성될 수 있다.

    클라이언트는 서버의 복잡도와 관계없이 서버와 연결되는 포인트만 알면 된다.

    클라이언트 - 서버 아키텍처

    REST 서버API를 제공하고 클라이언트는 사용자 정보를 관리하는 구조로 분리해 설계한다.

    이러한 구성으로 , 서로에 대한 의존성을 낮출 수 있다.


    - 4. REST의 URI 설계 규칙

    URI의 마지막에는 / 를 붙이지 않는다.

    URI는 소문자로 작성한다.

    _ 대신 - 를 사용한다.

    URL에는 행위(동사)가 아닌 결과(명사)를 포함한다.

    URI에 파일의 확장자는 포함하지 않는다.



        _____________________🐢🎈____________________


    03장 개발 환경 구성

    jdk 인텔리제이 이클립스

        _____________________🐢🎈____________________

Designed by Tistory / Custom by 얼거스