웹 어플리케이션과 싱글톤

  • 우리가 만들었던 스프링 없는 순수한 DI 컨테이너인 AppConfig는 요청을 할 때 마다 객체를 새로 생성한다.
  • 고객 트래픽이 초당 100이 나오면 초당 100개 객체가 생성되고 소멸된다! => 메모리 낭비가 심하다.
  • 해결방안은 해당 객체가 딱 1개만 생성되고, 공유하도록 설계하면 된다. => 싱글톤 패턴

싱글톤 패턴

  • 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.
  • private 생성자를 사용해서 외부에서 임의로 new 키워드를 사용하지 못하도록 막아야 한다.

싱글톤 패턴을 적용한 예제 코드

package hello.core.singleton;

public class SingletonService {

    private static final SingletonService instance = new SingletonService();

    public static SingletonService getInstance() {
        return instance;
    }

    // private 생성자를 써서 밖에서 new 못 함!
    private SingletonService() {
    }

    public void logic () {
        System.out.println("싱글톤 로직 호출");
    }
}
  1. static 영역에 객체 instance를 미리 하나 생성해서 올려둔다.
  2. 이 객체 인스턴스가 필요하면 오직 getInstance() 메서드를 통해서만 조회할 수 있다. 이 메서드를 호출하면 항상 같은 인스턴스를 반환한다.
  3. 딱 1개의 객체 인스턴스만 존재해야 하므로, 생성자를 private으로 막아서 혹시라도 외부에서 new 키워드로 객체 인스턴스가 생성되는 것을 막는다.

싱글톤 패턴을 사용하는 테스트 코드

@Test
@DisplayName("싱글톤 패턴을 사용한 객체 사용")
void singletonServiceTest() {
    SingletonService singletonService1 = SingletonService.getInstance();
    SingletonService singletonService2 = SingletonService.getInstance();

    System.out.println("singletonService1 = " + singletonService1);
    System.out.println("singletonService2 = " + singletonService2);

    // isSameAs == 인스턴스가 같은지 비교
    assertThat(singletonService1).isSameAs(singletonService2);
}

싱글톤 패턴의 문제점

  • 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
  • 의존관계상 클라이언트가 구체 클래스에 의존한다. => DIP를 위반한다.
  • 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
  • 테스트하기 어렵다.
  • 내부 속성을 변경하거나 초기화 하기 어렵다.
  • private 생성자로 자식 클래스를 만들기 어렵다.
  • 결론적으로 유연성이 떨어진다.
  • 안티패턴으로 불리기도 한다.

싱글톤 컨테이너 

  • 스프링 컨테이너는 싱글턴 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다
  • 스프링 컨테이너의 이런 기능 덕분에 싱글턴 패턴의 모든 단점을 해결하면서 객체를 싱글톤으로 유지할 수 있다.
  • 싱글톤 패턴을 위한 지저분한 코드가 들어가지 않아도 된다.
  • DIP, OCP, 테스트, private 생성자로 부터 자유롭게 싱글톤을 사용할 수 있다.

스프링 컨테이너를 사용하는 테스트 코드

@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer() {
    ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    MemberService memberService1 = ac.getBean("memberService", MemberService.class);
    MemberService memberService2 = ac.getBean("memberService", MemberService.class);

    // 참조값이 같은 것을 확인
    System.out.println("memberService1 = " + memberService1);
    System.out.println("memberService1 = " + memberService2);

    // memberService1 != memberService2
    assertThat(memberService1).isSameAs(memberService2);
}
  • 스프링 컨테이너 덕분에 고객의 요청이 올 때 마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유 해서 효율적으로 재사용할 수 있다.

싱글톤 방식의 주의점

  • 스프링 빈은 항상 무상태(stateless)로 설계하자!

@Configuration 과 싱글톤

 

@Configuration 과 바이트코드 조작의 마법

스프링 컨테이너와 스프링 빈

스프링 컨테이너 생성

//스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
  • ApplicationContext
    • 스프링 컨테이너라고 함
    • 인터페이스임
    • XML 또는 애노테이션 기반의 자바 설정 클래스로 만들 수 있음
  • 스프링 컨테이너 생성 과정
    1. 스프링 컨테이너 생성
    2. 스프링 빈 등록
      • 빈 이름은 메서드 이름을 사용
      • 빈 이름을 직접 부여 가능
        • ex) @Bean(name="memberService2")
    3. 스프링 빈 의존관계 설정 - 준비
    4. 스프링 빈 의존관계 설정 - 완료
      • 스프링 컨테이너는 설정 정보를 참고하여 의존관계를 주입(DI)
  • 스프링 컨테이너 생성 과정 그림

1. 스프링 컨테이너 생성
2. 스프링 빈 등록
3. 스프링 빈 의존관계 설정 - 준비
4. 스프링 빈 의존관계 설정 - 완료

컨테이너에 등록된 모든 빈 조회

// 테스트 코드

 

  • 모든 빈 조회
    • ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회한다.
    • ac.getBean() : 빈 이름으로 빈 객체(인스턴스)를 조회한다.
  • 단축키
    • iter + Tab: for 문 자동 완성
    • soutm: 메서드명 출력
    • soutv: 변수명 출력

스프링 빈 조회 - 기본

  • ac.getBean(빈 이름, 타입)
  • ac.getBean(타입)

// 테스트 코드

  • 단축키
    • command + e: 이전 파일로 가기 (최근 파일)

스프링 빈 조회 - 동일 타입 둘 이상

  • 타입으로 조회시 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생
  • 이때는 빈 이름을 지정하자. ac.getBeansOfType() 을 사용하면 해당 타입의 모든 빈을 조회할 수 있다.

// 테스트 코드

 

스프링 빈 조회 - 상속 관계

  • 부모 타입으로 조회하면, 자식 타입도 함께 조회

BeanFactory 와 ApplicationContext

BeanFactory

  • 스프링 컨테이너의 최상위 인터페이스
  • 스프링 빈을 관리하고 조회하는 역할을 담당
  • getBean() 을 제공
  • 지금까지 우리가 사용했던 대부분의 기능은 BeanFactory가 제공하는 기능

ApplicationContextBeanFactory 

    • BeanFactory 기능을 모두 상속받아서 제공
    • 그 외 수많은 부가 기능 제공

  • 메시지 소스를 활용한 국제화 기능
    • ex) 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력
  • 환경변수
    • 로컬, 개발, 운영 등을 구분해서 처리
  • 애플리케이션 이벤트
    • 이벤트를 발행하고 구독하는 모델을 편리하게 지원
  • 편리한 리소스 조회
    • 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회

정리

  • ApplicationContext는 BeanFactory의 기능을 상속 받음
  • ApplicationContext는 빈 관리 기능 + 편리한 부가 기능을 제공
  • BeanFactory를 직접 사용할 일은 거의 없고 부가기능이 포함된 ApplicationContext를 사용
  • BeanFactory나 ApplicationContext를 스프링 컨테이너라고 함

다양한 설정 형식 지원 - 자바 코드, XML

  • 애노테이션 기반 자바 코드 설정
    • new AnnotationConfigApplicationContext(AppConfig.class)
  • XML 설정
    • new GenericXmlApplicationContext(AppConfig.xml)

// xml 예제 코드

스프링 빈 설정 메타 정보 - BeanDefinition

  • 스프링은 어떻게 다양한 설정 형식을 지원하는 것일까? => 핵심은 BeanDefinition 이라는 추상화!
  • 쉽게 이야기해서 역할과 구현을 개념적으로 나눈 것이다!
    • XML을 읽어서 BeanDefinition을 만들면 된다.
    • 자바 코드를 읽어서 BeanDefinition을 만들면 된다.
    • 스프링 컨테이너는 자바 코드인지, XML인지 몰라도 된다. 오직 BeanDefinition만 알면 된다.
  • BeanDefinition 을 빈 설정 메타정보라 한다.
    • @Bean , <bean> 당 각각 하나씩 메타 정보가 생성된다.
  • 스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성한다.

 

// 예제 코드

새로운 할인 정책 개발

    • 갑자기 기획이 바뀌어 정률 정책 할인도 구현
      • DiscountPolicy 클래스를 구현한 RateDiscountPolicy 클래스
      • package hello.core.discount;
        
        import hello.core.member.Grade;
        import hello.core.member.Member;
        
        public class RateDiscountPolicy implements DiscountPolicy {
        
            private int discountPercent = 10;
        
            @Override
            public int discount(Member member, int price) {
                if (member.getGrade() == Grade.VIP) {
                    return price * discountPercent / 100;
                } else {
                    return 0;
                }
            }
        }
    • 테스트 케이스 짜기
      • command + shift + t: 테스트 클래스 만들기
      • package hello.core.discount;
        
        import hello.core.member.Grade;
        import hello.core.member.Member;
        import org.junit.jupiter.api.DisplayName;
        import org.junit.jupiter.api.Test;
        
        import static org.assertj.core.api.Assertions.*;
        
        class RateDiscountPolicyTest {
        
            RateDiscountPolicy discountPolicy = new RateDiscountPolicy();
        
            @Test
            @DisplayName("VIP는 10% 할인이 적용되어야 한다.")
            void vip_o() {
                // give
                Member member = new Member(1L, "memberVIP", Grade.VIP);
                // when
                int discount = discountPolicy.discount(member, 10000);
                // then
                assertThat(discount).isEqualTo(1000);
            }
        
            @Test
            @DisplayName("VIP가 아니면 할인이 적용되지 않아야 한다.")
            void vip_x() {
                // give
                Member member = new Member(2L, "memberBASIC", Grade.BASIC);
                // when
                int discount = discountPolicy.discount(member, 10000);
                // then
                assertThat(discount).isEqualTo(0);
            }
        }

새로운 할인 정책 적용과 문제점

  • 할인 정책을 변경하려면 클라이언트인 OrderServiceImpl 코드를 고쳐야 한다.
  • public class OrderServiceImpl implements OrderService {
    	// private final DiscountPolicy discountPolicy = new FixedDiscountPolicy();
    	private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
    }
  • DIP 위반
    • 주문 서비스 클라이언트(OrderServiceImpl)는 추상(인터페이스) 뿐만 아니라 구체(구현) 클래스에도 의존하고 있다.
      • 추상(인터페이스) 의존: DiscountPolicy
      • 구체(구현) 클래스: FixDiscountPolicy , RateDiscountPolicy
  • OCP 위반
    • 변경하지 않고 확장할 수 있다고 했는데 지금 코드는 기능을 확장해서 변경하면 클라이언트 코드에 영향을 준다.
    • 실제 의존관계 클래스 다이어그램
  • 해결 방법?
    • 인터페이스에만 의존하도록 설계를 변경하자.
    • 누군가가 클라이언트인 OrderServiceImpl 에 DiscountPolicy 의 구현 객체를 대신 생성하고 주입해주어야 한다.

관심사의 분리

  • AppConfing 의 등장 (= 공연 기획자)
    • 애플리케이션의 전체 동작 방식을 구성(config)하기 위해 1. 구현 객체를 생성하고 2. 연결하는 책임을 가지는 별도의 설정 클래스
    • public class AppConfig {
      
          public MemberService memberService() {
              return new MemberServiceImpl(memberRepository());
          }
      
          private MemberRepository memberRepository() {
              return new MemoryMemberRepository();
          }
      
          public OrderService orderService() {
              return new OrderServiceImpl(memberRepository(), discountPolicy());
          }
      
          public DiscountPolicy discountPolicy() {
              return new FixDiscountPolicy();
          }
      }
    • 구현 객체를 생성
    • 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입
      • public class MemberServiceImpl implements MemberService{
        
            private final MemberRepository memberRepository;
        
            public MemberServiceImpl(MemberRepository memberRepository) {
                this.memberRepository = memberRepository;
            }
        
            @Override
            public void join(Member member) {
                memberRepository.save(member);
            }
        
            @Override
            public Member findMember(Long memberId) {
                return memberRepository.findById(memberId);
            }
        }
    • 설계 변경으로 MemberServiceImpl 은 MemoryMemberRepository 를 의존하지 않는다!
    • 단지 MemberRepository 인터페이스만 의존한다.
    • 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부(AppConfing)에서 결정된다.
    • 클래스 다이어그램
    • 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 DI(Dependency Injection) 우리말로 의존관계 주입 또는 의존성 주입이라 한다.
    • 테스트 코드
      • package hello.core.member;
        
        import hello.core.AppConfig;
        import org.assertj.core.api.Assertions;
        import org.junit.jupiter.api.BeforeEach;
        import org.junit.jupiter.api.Test;
        
        public class MemberServiceTest {
        
            MemberService memberService;
        
            @BeforeEach
            public void beforeEach() {
                AppConfig appConfig = new AppConfig();
                memberService = appConfig.memberService();
            }
        
            @Test
            void join() {
                // give
                Member member = new Member(1L, "memberA", Grade.VIP);
        
                // when
                memberService.join(member);
                Member findMember = memberService.findMember(1L);
        
                // then
                Assertions.assertThat(member).isEqualTo(findMember);
            }
        }
      • @BeforeEach 는 각 테스트를 실행하기 전에 호출됨
      • command + e: 히스토리 보여줌
      • command + option + m: extract method

새로운 구조와 할인 정책 적용

  • AppConfig의 등장으로 애플리케이션이 크게 사용 영역과, 객체를 생성하고 구성(Configuration)하는 영역으로 분리되었다.

  • FixDiscountPolicy -> RateDiscountPolicy 로 변경해도 구성 영역만 영향을 받고 사용 영역은 전혀 영향을 받지 않는다.

좋은 객체 지향 설계의 5가지 원칙 적용

1. SRP 단일 책임 원칙: 한 클래스는 하나의 책임만

  • SRP 단일 책임 원칙을 따르면서 관심사를 분리함 
  • 구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당 
  • 클라이언트 객체는 실행하는 책임만 담당

2. DIP 의존관계 역전 원칙: 프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안 된다.

  • AppConfig 가 객체 인스턴스를 클라이언트 코드 대신 생성해서 클라이언트 코드에 의존관계를 주입 (= DI)

3.  OCP 개방-폐쇄 원칙: 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야

  • AppConfig가 의존관계를 FixDiscountPolicy -> RateDiscountPolicy 로 변경해서 클라이언트 코드에 주입하므로 클라이언트 코드는 변경하지 않아도 됨

IoC, DI, 그리고 컨테이너

제어의 역전 IoC(Inversion of Control)

  • 기존 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결하고, 실행했다. 한 마디로 구현 객체가 프로그램의 제어 흐름을 스스로 조종했다. 개발자 입장에서는 자연스러운 흐름이다. 
  • 반면에 AppConfig가 등장한 이후에 구현 객체는 자신의 로직을 실행하는 역할만 담당한다. 프로그램의 제어 흐름은 이제 AppConfig가 가져간다.

의존관계 주입 DI(Dependency Injection)

  • 애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것을 의존관계 주입이라 한다.
  • 의존관계 주입을 사용하면 클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있다.

IoC 컨테이너, DI 컨테이너

  • AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 IoC 컨테이너 또는 DI 컨테이너라 한다.
  • 의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 한다.
  • 또는 어셈블러, 오브젝트 팩토리 등으로 불리기도 한다.

스프링으로 전환하기

  • AppConfig 코드
    • @Configuration
      public class AppConfig {
      
          @Bean
          public MemberService memberService() {
              return new MemberServiceImpl(memberRepository());
          }
      
          @Bean
          public MemberRepository memberRepository() {
              return new MemoryMemberRepository();
          }
      
          @Bean
          public OrderService orderService() {
              return new OrderServiceImpl(memberRepository(), discountPolicy());
          }
      
          @Bean
          public DiscountPolicy discountPolicy() {
              // return new FixDiscountPolicy();
              return new RateDiscountPolicy();
          }
      }
  • MemberApp 에 스프링 컨테이너 적용
    • public class MemberApp {
      
          public static void main(String[] args) {
             	// MemberService memberService = new MemberServiceImpl();
              // AppConfig appConfig = new AppConfig();
              // MemberService memberService = appConfig.memberService();
      
              ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
              MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
      
              Member member = new Member(1L, "memberA", Grade.VIP);
              memberService.join(member);
      
              Member findMember = memberService.findMember(1L);
              System.out.println("new member = " + member.getName());
              System.out.println("find member = " + findMember.getName());
          }
      }
  • ApplicationContext 스프링 컨테이너라고 한다.
  • 기존에는 개발자가 AppConfig 를 사용해서 직접 객체를 생성하고 DI를 했지만, 이제부터는 스프링
    컨테이너를 통해서 사용한다.
  • 스프링 컨테이너는 @Configuration 이 붙은 AppConfig 를 설정(구성) 정보로 사용한다. 여기서 @Bean
    이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. 이렇게 스프링 컨테이너에
    등록된 객체를 스프링 빈이라 한다.
  • 스프링 빈은 @Bean 이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다. (memberService, orderService)
  • 스프링 빈은 applicationContext.getBean() 메서드를 사용해서 찾을 수 있다.

1. 리액트의 등장 배경

  • 기존의 프레임워크(Angular, Backbone.js, ..., Vue.js)의 특징
    • 주로 MVC, MVVM, MVW 아키텍처로 애플리케이션을 구조화한다.
    • 뷰를 변형(mutate)하며 변경된 사항을 뷰에 반영한다.
    • 단점
      • 애플리케이션 규모가 크면 상당히 복잡해지고 제대로 관리하지 않으면 성능이 떨어질 수 있다.
  • 이러한 단점을 보완하는 방법은 없을까?
    • 데이터가 변할 때마다 어떤 변화를 줄지 고민 X → 기존 뷰를 냘려 버리고 처음부터 새로 렌더링
    • 장점
      • 애플리케이션 구조가 매우 간단해지고, 작성해야 할 코드 양도 많이 줄어든다.
    • 근데 이게 가능해?
      • CPU 점유율도 크게 증가하고, DOM은 느리고, 메모리도 많이 사용할 것이고, 새로 렌더링 하면 끊김 현상이 일어나지 않을까?
    • 페이스북 개발 팀에서 앞서 설명한 방식으로 최대한 성능을 아끼고 편한한 사용자 경험을 제공하면서 구현하고자 개발한 것이 바로 리액트이다.

2. 리액트 이해

  • 오직 View만 신경 쓰는 라이브러리이다.

2.1. 컴포넌트

  • 특정 부분이 어떻게 생길지 정하는 선언체이다.
  • 재사용이 가능한 API로 수많은 기능들이 내장되어있다.

2.2. 리렌더링

  • 리렌더링은 리액트가 데이터가 변할 때마다 새롭게 리렌더링하면서 성능을 아끼고, 최적의 사용자 경험을 제공하는 비결이다.
  • 두 가지 과정으로 구성되어있다.

2.2.1. 초기 렌더링

  • 맨 처음 어떻게 보일 지를 정하는 초기 렌더링이 필요하다.
  • render 함수
    • 컴포넌트가 어떻게 생겼는지 정의하는 역할을 한다.
    • 뷰가 어떻게 생겼고 어떻게 작동하는지에 대한 정보를 지닌 객체를 반환한다.
    • 컴포넌트 내부에는 또 다른 컴포넌트가 들어갈 수 있는데, 이때 render 함수를 실행하면 그 내부에 있는 컴포넌트들도 재귀적으로 렌더링 된다.
  • 초기 렌더링 과정
    • 최상위 컴포넌트를 렌더링 → 지니고 있는 정보들을 사용하여 HTML 마크업을 만듦 → 실제 페이지의 DOM 요소 안에 주입

2.2.2. 조화 과정

  • 리액트에서 뷰를 업데이트할 때는 "업데이트 과정을 거친다"라고 하기보다는 "조화 과정(reconciliation)을 거친다"라고 하는 것이 정확한 표현이다.
  • 컴포넌트는 데이터를 업데이트했을 때 단순히 업데이트한 값을 수정하는 것이 아니라, 새로운 데이터를 가지고 render 함수를 또다시 호출한다.
  • 이때 render 함수가 반환하는 결과를 곧바로 DOM에 반영 X → 이전에 render 함수가 만들었던 컴포넌트 정보와 현재 render 함수가 만든 컴포넌트 정보를 비교한다.
  • 두 가지 뷰를 최소한의 연산으로 비교한 후, 둘의 차이를 알아내 최소한의 연산으로 DOM 트리를 업데이트한다.

3. 리액트의 특징

3.1. Virtual DOM

3.1.1. DOM이란?

  • Document Object Model의 약어
  • 객체로 문서 구조를 표현하는 방법으로 XML이나 HTML로 작성한다.
  • 트리 형태로 구성
  • DOM은 과연 느릴까?
    • DOM의 치명적인 문제점은 동적 UI에 최적화되어 있지 않다는 것이다.
    • HTML은 정적이지만 자바스크립트를 사용하여 이를 동적으로 만든다.
    • DOM 자체는 빠르다. DOM 자체를 읽고 쓸 때의 성능은 자바스크립트 객체를 처리할 때의 성능과 다르지 않음.
    • 단, DOM에 변화가 일어나면 웹 브라우저가 CSS를 다시 연산하고, 레이아웃을 구성하고, 페이지를 리페인트하는 과정에서 시간이 허비되는 것이다.
  • 해결법?
    • Virtual DOM
      • DOM 업데이트를 추상화함으로써 DOM 처리 횟수를 최소화하고 효율적으로 진행한다.

3.1.2. Virtual DOM

  • 실제 DOM에 접근하여 조작하는 대신, 이를 추상화한 자바스크립트 객체를 구성하여 사용한다.
  • 리액트에서 데이터가 변하여 DOM을 업데이트하는 과정
    1. 데이터를 업데이트하면 전체 UI를 Virtual DOM에 리렌더링한다.
    2. 이전 Virtual DOM에 있던 내용과 현재 내용을 비교한다.
    3. 바뀐 부분만 실제 DOM에 적용한다.
  • 리액트 + Virtual DOM → 업데이트 처리 간결성!

3.2. 기타 특징

  • 리액트는 오직 뷰만 담당한다.
  • 리액트는 프레임워크가 아니라 라이브러리이다.
  • 기타 기능은 직접 구현하거나 라이브러리를 사용해야 한다.
  • 리액트는 다른 웹 프레임워크나 라이브러리와 혼용할 수도 있다.

4. 작업 환경 설정

  1. Node.js
    • 왜 사용하지?
      • 프로젝트를 개발하는 데 필요한 주요 도구들이 Node.js를 사용한다.
        • 바벨
          • ES6를 호환시켜준다.
        • 웹팩
          • 모듈화 된 코드를 한 파일로 합치고(번들링) 코드를 수정할 때마다 웹 브라우저를 리로딩 기능 등을 수행한다.
    • nvm 사용한다.
      • Node,js를 여러 버전으로 설치하여 관리해 준다.
  2. yarn
    • npm보다 더 빠르며 효율적인 캐시 시스템과 기타 부가 기능을 제공한다.
  3. create-react-app으로 프로젝트 생성하기
    • yarn create react-app (프로젝트 이름)
    • cd (프로젝트 이름)
    • yarn start

현재 다니고 있는 빅테크 회사에

백앤드 개발 직무로 지원을 했지만

"프론트앤드도 공부를 해봐" 라는 파트장님의 말과 함께

정규직이 되어서는 프론트 개발도 많이 하게 되었습니다. ㅋㅋ

 

학교 수업에서 HTML, CSS, 순수 Javascript로 홈페이지를 만들어 본 것과

iOS 개발을 조금 해본 것이 다이기 때문에

"과연 프론트앤드 실무 개발을 잘 할 수 있을까..?" 라는 생각이 들었지만

『리액트를 다루는 기술』 책으로 공부해보니 React를 재밌고 나름대로 수월하게 공부할 수 있었습니다.

 

Javascript는 알지만 React를 처음 공부하시는 분들에게 이 책을 추천드립니다.

이 책이 좋았던 이유는 Hooks, 리덕스에 대해서 잘 설명이 되어있는 것과 이해를 돕는 디테일한 예제가 도움이 되었기 때문입니다.

(광고 아님 xxx)

 

벌써 이 책으로 공부를 한 지 1년이 되었는데

공부했던 내용을 블로그에 적어보려고 합니다.

 

http://www.yes24.com/Product/Goods/78233628

 

리액트를 다루는 기술 - YES24

리액트 베스트셀러 1위, 본문과 소스 전면 업그레이드기본기를 꼼꼼하게! 실전에서 효과적으로 활용하는 방법까지 알차게 배우자『리액트를 다루는 기술』(개정판)은 리액트 16.8 버전에 Hooks라

www.yes24.com

'React > [도서] 리액트를 다루는 기술' 카테고리의 다른 글

[1장] 리액트 시작  (0) 2021.07.22

1. 이야기

  • EJB 엔티티빈 (ORM) --화남--> 하이버네이트 --자바표준--> JPA
  • 표준 인터페이스 JPA <--JPA 를 구현-- 하이버네이트 등

2. 스프링 역사

  • 스프링 이름은 전통적인 EJB라는 겨울을 넘어 새로운 시작이라는 뜻으로 지었다고 함!

3. 스프링 생태계

  • 스프링 부트
    • 스프링을 편하게 사용할 수 있도록 지원, 최근에는 기본으로 사용
    • Tomcat 같은 웹 서버를 내장해서 별도의 웹 서버를 설치하지 않아도 됨
    • 손쉬운 빌드 구성을 위한 starter 종속성 제공
    • 스프링과 3rd party 라이브러리 자동 구성

4. 스프링을 왜 만들었냐?

  • 객체 지향 언어의 강력한 특징을 잘 살려냄!
  • 객체 지향 특징
    • 추상화
    • 캡슐화
    • 상속
    • 다형성
  • 다형성
    • 역할구현의 분리
    • 장점: 클라이언트가 내부 구조를 몰라도 됨
    • 다른 대상으로 대체 가능. 유연. 변경 용이
    • 역할 = 인터페이스
    • 구현 = 인터페이스를 구현한 클래스, 구현 객체
    • 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다.
    • 즉, MemberService 를 변경하지 않고, MemberRepository <- MemoryMemberRepository || JdbcMemberRepository 가 가능하다.
    • 인터페이스를 잘 설계하는 것이 중요! (잘하는 개발자)
  • 스프링은 다형성을 극대화해서 이용할 수 있도록 지원!
    • ex) IoC, DI

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

6.1. SOLID

  1. SRP: 단일 책임 원칙 (Single Responsibility Principle)
  2. OCP: 개방-폐쇄 원칙 (Open/Closed Principle)
  3. LSP: 리스코프 치환 원칙 (Liskov Subsitution Principle)
  4. ISP: 인터페이스 분리 원칙 (Interface Segregation Principle)
  5. DIP: 의존관계 역전 원칙 (Despendency Inversion Principle)

6.2. SRP 단일 책임 원칙 (Single Responsibility Principle)

  • 한 클래스는 하나의 책임만 가져야 한다.
  • 중요한 기준은 변경이다. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것.

6.3. OCP 개방-폐쇠 원칙 (Open/Closed Principle)

  • 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
  • 문제점: 클라이언트 변경 필요
    •  
    • public class MemberService { private MemberRepository memberRepository = new MemoryMemberRepository(); }
    • public class MemberService {
      	// private MemberRepository memberRepository = new MemoryMemberRepository();
      	private MemberRepository memberRepository = new JdbcMemberRepository();
      }
  • 구현 객체를 변경하려면 클라이언트 코드를 변경해야 한다.
  • 분명 다형성을 사용했지만 OCP 원칙을 지킬 수 없다.
  • 객체를 생성하고 연관관계를 맺어주는 별도의 조립, 설정자가 필요하다. => 스프링 컨테이너의 역할. DI, IoC 컨테이너.

6.4. LSP 리스코프 치환 원칙 (Liskov substitution principle)

  • 프로그램의 객체는 프로그램의 정확성을 개뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
  • 다형성에서 하위 클래스의 인터페이스 규약을 다 지켜야 한다는 것. 다형성을 지원하기 위한 원칙. 인터페이스를 구현한 구편체는 믿고 사용하려면, 이 원칙이 필요하다.
  • ex) 자동차 인터페이스의 엑셀은 앞으로 가라는 기능. 뒤로 가게 구현하면 LSP 위반. 느리더라도 앞으로 가야함.

6.5. ISP 인터페이스 분리 원칙 (Interface segregation principle)

  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
  • 자동차 인터페이스 -> 운전 인터페이스, 정비 인터페이스로 분리
  • 사용자 클라이언트 -> 운전자 클라이언트, 정비사 클라이언트로 분리
  • 분리하면 정비 인터페이스 자체가 변해도 운전자 클라이언트에 영향을 주지 않음.

 

1. HTTPS

1.1. HTTPS와 HTTP

  • HTTP(HyperText Transfer Protocol)는 HyperText인 HTML 문서를 전송하기 위한 프로토콜이다.
  • 마지막에 S를 붙인다면 Secure라는 뜻으로 보안이 강화된 통신규약을 의미한다.
  • HTTP는 암호화가 되어있지 않은 방법으로 서버에 데이터를 전송하기 때문에 서버와 클라이언트가 서로 주고받는 메시지를 알아내기가 쉽다.
  • 그러므로 서버로 비밀번호나 계좌번호 등 중요한 데이터를 서버로 전송할 경우에는 HTTPS 프로토콜을 사용하여 통신하는 것이 중요하다.

1.2. HTTPS와 SSL

  • HTTPS는 SSL 프로토콜을 기반으로 돌아가는 프로토콜 중 하나다.

2. SSL

1.1. SSL 이란

  • Secure Socket Layer라는 암호 규약
  • SSL은 '보안 계층'이라는 독립적인 프로토콜 계층을 만들어, 위 그림과 같이 응용 계층과 전송 계층 사이에 속하게 된다.

출처: https://blog.naver.com/skinfosec2000/222135874222

1.2. SSL과 TLS

  • SSL과 TLS(Transport Layer Security)는 같은 뜻으로 말하며 TLS1.0은 SSL3.0을 계승한다.
  • 쉽게 생각하면 SSL의 New Version이 TLS이다.
  • 하지만 TLS라는 이름보단 SSL이라는 이름으로 더 많이 사용되고 있다.

1.3. SSL 인증서란

  • SSL 인증서란 클라이언트와 서버 간의 통신을 제3자가 보증을 해주는 문서이다.
  • 클라이언트가 서버에 접속하면 클라이언트는 이 인증서를 보고 신뢰할 수 있는지를 확인한 후 데이터를 보내는 등 다음 절차를 수행하게 된다.
  • 서버는 클라이언트에게 인증서를 전달한다.

1.4. SSL의 장점

  • 전달되는 내용이 다른 사람에게 노출되는 것을 막을 수 있다.
  • 클라이언트가 접속하려는 서버가 신뢰할 수 있는 서버 인지 알 수 있다.
  • 전달되는 내용이 악의적으로 변경되는 것을 막을 수 있다.

3. SSL 암호화 종류

3.1. 대칭키

3.1.1. 대칭키란

  • 대칭키 방식은 동일한 키로 암호화와 복호화를 할 수 있는 기법을 말한다.
  • ex) 1234를 사용하여 암호화하였다면 복호화도 1234를 입력해야 가능하다.
  • 암호화(=암호를 만드는 행위)를 할 때 사용하는 비밀번호를 키(key)라고 한다.

3.1.1. 단점

  • 클라이언트와 서버는 대화를 하기 위해서 반드시 대칭 키를 알고 있어야 한다. (= 키 배송 문제)
  • 그렇기 때문에 통신을 하기 앞서 키를 전달해야하는 과정이 필요하다.
  • 그런데 만약 중간에 대칭키가 유출된다면 HTTPS를 사용할 필요성이 사라진다.
  • 이런 단점을 보완하기 위해 나온 방식이 공개키 기법이다.

3.2. 공개키

3.2.1. 공개키란

  • 공개키 방식은 대칭키 방식과 다르게 2개의 키를 가지고 시작한다.
  • 그 중 하나는 공개키(public key)
  • 나머지 키를 비밀키(private key, 개인키/비밀키)라 부른다.
  • 비밀키는 자신만이 소지하고, 공개키는 타인에게 제공한다.

  • 동작 원리는 다음과 같다. 비밀키로 암호화하면 공개키로 복호화 한다. 서버에게 !@#$라는 text를 전달한다.
  • 서버는 클라이언트가 보낸 !@#$라는 단어를 비밀키로 복호화하여서 1234라는 것을 확인한다.
  • ex) 클라이언트가 서버의 공개키를 가지고 1234(정보)를 암호화하여
  • 공개키로 암호화하면 비밀키로 복호화 한다.

  • 공개키는 공개되어 있으며 보통 디지털 인증서안에 포함되어 있다. 이것을 우리는 전자서명이라고 부른다.
  • 그렇기 때문에 공개키가 존재한다는건 서버의 신원이 안전하다고 볼 수 있다.

3.2.1. 단점

  • 공개키 암호화 방식의 알고리즘은 계산이 느리다는 단점이 있다.

4. SSL 통신 과정

  • 통신을 위해선 핸드쉐이크 -> 세션 -> 세션 종료의 과정을 거친다.
  • 암호화된 HTTP 메시지를 교환하기 전에 클라이언트와 서버는 SSL 핸드쉐이크를 진행한다.
  • SSL 핸드쉐이킹에서 핵심은 공개키와 대칭키 2가지 방법을 함께 사용한다는 점이다.

Step 1: Client Hello

  • Client Hello : Server에 접근한다.
    • 클라이언트는 자신의 브라우저가 지원할 수 있는 암호화 방식(Cipher Suite)을 먼저 제시한다.
    • 그리고 랜덤 데이터를 생성하여 추가로 전송한다.

 

Step 2: Server Hello

  • Server Hello : Server가 응답한다.
    • 가장 안정한 암호화 방식 (클라이언트가 제시한 암호화 방식 중 하나를 선정)
    • 서버 자신의 인증서 with 공개키
    • 서버에서 생성한 램덤 데이터

Step 3: 인증서 확인 pre master secret 암호화 

  • 클라이언트는 내장되어있는 CA 리스트에서 각 CA의 공개키를 이용하여 서버가 보낸 인증서를 복호화한다.
  • 만약 CA 리스트에 없는 인증서라면 사용자에게 경고의 메시지를 띄운다.
  • 복호화에 성공했다면 인증서는 CA의 개인키로 암호화된 문서임이 암시적으로 보증된 것이다. 인증서를 전송한 서버를 믿을 수 있게 된 것이다.
  • 서버의 랜덤 데이터와 클라이언트가 생성한 랜덤 데이터를 조합하여 pre master secret 키를 생성한다.
  • 이 키는 뒤에서 살펴볼 세션 단계에서 데이터를 주고 받을 때 암호화하기 위해서 사용된다.

Step 4~5: pre master secret 전달 및 복호화

  • 이 때 사용할 암호화 기법은 대칭키이기 때문에 pre master secret 키는 제 3자에게 절대로 노출되어서는 안 된다.
  • 그럼 문제는 이 pre master secret 값을 어떻게 서버에게 전달할 것인가이다.
  • 이 때 사용하는 방법이 바로 공개키 방식이다. 
  • Step 2에서 받은 공개키로 pre master secret 키를 암호화하여 서버로 전송하면 서버는 자신의 비공개키로 안전하게 복호화 할 수 있다.

Step 6: master secret 및 session key (대칭키) 생성

  • 서버와 클라이언트는 모두 일련의 과정을 거쳐 pre master secret 값을 master secret 값으로 만든다.
  • master secret는 session key를 생성하는데 이 session key 값을 이용해서 서버와 클라이언트는 데이터를 대칭키 방식으로 암호화한 후에 주고 받는다.
  • 클라이언트와 서버는 핸드쉐이크 단계의 종료를 서로에게 알린다.
  • 클라이언트와 서버간의 세션이 형성되고즉 session key를 활용한 대칭키 방식으로 데이터를 주고 받는다.

 

Reference

'HTTP' 카테고리의 다른 글

SSL 적용하기 (HTTPS)  (0) 2021.05.03

+ Recent posts