POJO란 무엇인가?

스프링의 목적은 애플리케이션 개발의 복잡함을 줄여주는 것 또는 효과적으로 대응하게 해주는 것입니다.
스프링이 지향하는 목적은 스프링은 엔터프라이즈 서비스 기술과 POJO라는 애플리케이션 로직을 담은 코드를 분리했다는 뜻입니다.
분리됐지만 반드시 필요한 엔터프라이즈 서비스 기술을 POJO방식으로 개발된 애플리케이션 핵심 로직을 담은 코드에 제공한다는 것이 스프링의 가장 강력한 특징과 목표입니다.

 

스프링의 핵심 POJO

스프링은 애플리케이션은 POJO를 이용해서 만든 애플리케이션 코드와, POJO가 어떻게 관계를 맺고 동작하는지를 정의해놓은 설계정보로 구분됩니다.
DI의 기본 아이디어는 유연하게 확장 가능한 오브젝트를 만들어두고 그 관계는 외부에서 다이내믹하게 설정해준다는 것입니다.
이런 DI의 개념을 애플리케이션 전반에 걸쳐 적용하는 것이 스프링의 프로그래밍 모델입니다.
스프링의 주요 기술인 IoC/DI, AOP와 PSA는 애플리케이션을 POJO로 개발할 수 있게 해주는 가능 기술이라고 불립니다.

 

 

POJO란 무엇인가?

POJO는 Plain Old Java Object의 첫 글자를 따서 만든 약자입니다.
최근 몇 년간 자바에서 유행어처럼 사용되고 있는 이 단어는 마틴 파울러가 2000년에 컨퍼런스 발표를 준비하다가 만들어낸 용어라고 합니다.
당시 인기가 있던 EJB는 복잡하고 제한이 많은 기술이었습니다.
EJB를 사용하는 것보다 자바의 단순한 오브젝트를 이용해 애플리케이션의 비즈니스 로직을 구현하는 편이 낫다고 판단한 마틴 파울러는 개발자들이 왜 자바의 단순한 오브젝트를 사용하지 않는지 찾아보았습니다.
이유는 단순히 EJB와 같은 이름이 없어서였습니다.
그래서 POJO라는 이름을 붙인 후 POJO프로그래밍에 대한 개발자들의 관심이 높아졌고 POJO를 지원한다는 걸 장점으로 내세우는 많은 프레임워크와 기술들이 나오기 시작했습니다.

 

POJO의 조건

특정 규약에 종속되지 않는다.
POJO는 자바 언어와 꼭 필요한 API 외에는 종속되지 않아야 합니다.
스트럿츠 1과 같이 특정 클래스를 상속해서 만들어야 하는 규약이 있는 경우에도 POJO가 아닙니다.
스트럿츠는 MVC 패턴에서 Controller 역할을 하는 웹 애플리케이션 프레임워크입니다. 
특정 규약을 따라 만들게 한다면 규약에서 제시하는 특정 클래스를 상속해야 합니다.
그럴 경우 자바의 단일 상속 제한 때문에 더 이상 해당 클래스에 객체지향적인 설계 기법을 적용하기 어려워지는 문제가 생깁니다.
또한 이미 특정 규약에 종속되어 있기 때문에 다른 환경으로 이전이 힘들어지는 문제점이 생깁니다.

 

특정 환경에 종속되지 않는다.

특정 환경에 종속적이어야만 동작하는 오브젝트도 POJO라고 할 수 없습니다.
JNDI가 없는환경에서 그대로 사용하기 힘든 EJB처럼 특정 환경이 의존 대상 검색 방식에 종속적이라면 POJO라고 할 수 없습니다.
비즈니스 로직을 담고 있는 POJO 클래스는 웹이라는 환경정보나 웹 기술을 담고 있는 클래스나 인터페이스를 사용해서는 안 됩니다.
웹이라는 환경으로 제한해버리기 때문입니다.
그리고 웹 서버에 올리지 않으면 독립적으로 테스트하기 어려워집니다.

 

어노테이션을 사용하면?

XML에 담겨있는 설정정보를 자바 코드로 가져왔어도, 그 때문에 환경에 종속되지 않는다면 POJO라고 할 수 있습니다.
하지만 어노테이션이나 엘리먼트 값에 특정 기술과 환경에 종속적인 정보를 담고 있다면 그때는 POJO라고 할 수 없습니다.

 

특정 기술이나 환경에 종속적이지 않다면 모두 POJO일까?

POJO는 객체지향적인 자바 언어의 기본에 충실하게 만들어져야 합니다.
그것이 POJO라는 이름을 붙이면서까지 단순한 자바 오브젝트에 집착하는 이유입니다.
자바 언어와 문법만 사용했다고 해서 객체지향적으로 만들어졌다고 볼 수는 없습니다.
재사용이 불가능할 정도로 다른 레이어와 영역의 코드와 강한 결합을 가지고 만들어진 경우와 OOP를 생각하지 않고 단순 if/switch 문으로 만들었다면, 객체지향적인 자바 오브젝트라고 할 수 없습니다.
이런 식으로 개발이 진행된다면 특정 기술과 환경에 종속적이지 않았지만, POJO라고 부를 수 없습니다.

 

그럼 POJO라고 부를려면 어떻게 해야 할까?

객체지향적인 원리에 충실하면서, 환경과 기술에 종속되지 않고 재사용이 될 수 있는 방식으로 설계된 오브젝트를 말합니다.

POJO의 장점 

POJO의 장점은 POJO조건 그대로가 장점이 됩니다.
특정 기술과 환경에 종속되지 않는 오브젝트는 깔끔한 코드가 될 수 있습니다.
매우 유연한 방식으로 원하는 레벨에서 코드를 빠르고 명확하게 테스트할 수 있습니다.
객체지향적인 설계를 자유롭게 적용할 수 있습니다.

 

POJO 프레임워크

스프링은 POJO를 이용한 엔터프라이즈 애플리케이션 개발을 목적으로 하는 프레임워크라고 했습니다.
POJO 프로그램이 가능하도록 기술적인 기반을 제공하는 프레임워크를 POJO 프레임워크라고 합니다. 
스프링 프레임워크와 하이버네이트가 대표적인 POJO 프레임워크로 볼 수 있습니다.

 

후기

POJO에 나온 특정 기술이나 환경에 종속되지 않게 프로그래밍하는 것은 예전부터 계속 들어왔던 말입니다.
스프링을 사용하고, 더 나은 프로그래밍을 하고싶다면 POJO의 개념을 알고 있을 필요가 있다고 생각합니다.

 

참조 자료: 토비의 스프링 3.1 vol.1

 

모놀리스란?

여기서 설명할 모놀리스는 주로 배포 단위를 말하는 것입니다.
시스템 기능을 함께 배포해야 할 때, 이를 모놀리스로 간주합니다.
모놀리스 시스템은 최소한 3가지 유형이 있습니다.
단일 프로세스 시스템 
분산 모놀리스 시스템
외부 블랙박스 시스템

 

단일 프로세스 모놀리스

모든 코드가 단일 프로세스로 배포되는 시스템입니다.
이런 단일 프로세스 시스템은 데이터를 거의 항상 데이터베이스에서 읽거나 데이터베이스에 저장하기 때문에, 단순하면서도 독자적인 분산 시스템입니다. 

 

모듈식 모놀리스

단일 프로세스 모놀리스의 하위 집합인 모듈식 모놀리스(modular monolith)는 일종의 변형입니다.
단일 프로세스는 별도 모듈로 구성되어 있으며 각 모듈은 독립적으로 동작할 수 있지만, 모듈식 모놀리스는 배포를 위해서 결합이 필요합니다.
소프트웨어를 모듈로 분해한 모놀리스 유형입니다.
기업에서 모듈식 모놀리스는 좋은 선택이 될 수 있습니다.
모듈 경계가 잘 정의되어 있으므로 병렬 작업을 많이 수행할 수 있지만 배포 고려사항은 훨씬 단순해지기 때문에, 분산 마이크로서비스 아키텍처에서 발생하는 문제를 피할 수 있습니다.
모듈식 모놀리스의 문제 중 하나는 코드 수준에서 지원하는 분해 능력이 데이터베이스 부문에는 부족하므로 향후 모놀리스를 끌어내고 싶을 경우에 중대한 문제에 직면한다는 사실입니다.

 

분산 모놀리스

분산 모놀리스(distributed monolith)는 여러 서비스로 구성되는 시스템이지만 어떤 이유로든 전체 시스템을 함께 배포해야 합니다.
분산 모놀리스는 분산 시스템의 모든 단점과 단일 프로세스 모놀리스의 단점까지 포함하며, 양쪽의 장점을 충분히 발휘하지 못합니다.
분산 모놀리스는 전형적으로 정보 은닉이나 비즈니스 기능의 응집력 같은 개념이 희박한 환경에서 등장합니다.
또한, 서비스 경계 지점에서 변경이 발생하기에 결합도가 매우 높은 아키텍처를 만들어내며, 지역적인 범위에서 나 쁜 의도가 없는 듯한 변경사항이 외부까지 영향을 미쳐 시스템의 다른 부분을 망가뜨립니다.

 

외부 블랙박스 시스템

마이그레이션 노력의 일환으로 본해하기를 원하는 몇몇 외부 소프트웨어를 모놀리스로 간주할 수 있습니다.
이런 소프트웨어로는 급여 시스템, CRM 시스템, HR 시스템 등이 있습니다.
이 소프트웨어 시스템들의 공통점은 우리에게는 코드를 변경할 역량이 없는 점입니다.
시스템 자체 인프라에 배포한 상용 소프트웨어거나 사용 중인 SaaS 제품일 수도 있습니다.

 

모놀리스의 문제점

단일 프로세스 모놀리스나 분산 모놀리스 등은 구현과 배포 결합도의 문제점에 더 취약합니다.
프로젝트를 진행하다 보면, 동일한 코드를 변경하기 원하는 다양한 개발자들이 있습니다. 
주체가 누가 되어서 코드를 관리할지 애매한 경우도 있습니다.
모놀리스를 사용한다고 해서 반드시 이 문제에 직면하는 것은 아니며, 마찬가지로 마이크로서비스 아키텍처를 사용한다고 해서 문제를 피할 수 있는 것도 아닙니다.
하지만 마이크로서비스 아키텍처는 각 서비스 중심으로 더 구체적인 경계가 있으므로 이 문제를 줄이기 위한 유연성이 더 뛰어납니다.
그리고 모놀리스는 특정 한 개의 장애가 시스템 전체에 영향을 주게 됩니다.
예상치 못한 결함이 발생하고 그에 따른 높은 테스트 비용과 높은 출시 사이클이 필요합니다.
이러한 장애에 대해 내성이 부족합니다.

 

모놀리스의 장점

단일 프로세스 모놀리스는 훨씬 더 간단한 개발자 워크플로우를 만들어냅니다.
또한 모니터링, 문제 해결, 전 구간 테스트를 단순화할 수 있습니다.
모놀리스는 모놀리스 자체 내에서 코드 재사용을 단순하게 만듭니다.
분산 시스템 내에서 코드 재사용을 원한다면, 코드를 복사할지, 라이브러리를 분해할지, 공유 기능을 서비스로 분리할지 등을 결정해야 합니다.
모놀리스를 사용하면 필요한 모든 코드가 있으므로 사용하기만 하면 됩니다.

 

모놀리스는 무조건 안좋은 것인가?

무조건 안좋지 않습니다.
안타깝게도 모놀리스라는 용어를 레거시와 동의어 취급하는 사람들이 많습니다.
모놀리스 아키텍처는 선택이며, 올바른 선택일 수도 있고, 아닐 수도 있습니다.
모놀리스 아키텍처나 마이크로서비스 아키텍처를 선택할 때는 우리가 처한 환경과 시스템 등을 고려하여 유연하게 판단할 필요가 있습니다.

 

후기

우리는 소프트웨어를 만드는 이유를 다시 생각해볼 필요가 있습니다.

 

참조 자료: 마이크로서비스 도입 이렇게 한다

 

디자인패턴 TemplateMethod Pattern

템플릿 메서도 패턴은 메서드에서 알고리즘의 골격을 정의합니다.
알고리즘의 여러 단계 중 일부는 서브클래스에서 구현할 수 있습니다.
템플릿 메소드를 이용하면 알고리즘의 구조는 그대로 유지하면서 서브클래스에서 특정 단계를 재정의 할 수 있습니다.

 

TemplateMethod Pattern 실제로 어떻게 적용할까요?

버거 레시피를 만들 예정입니다.
버거에 들어가는 재료들을 확인하는 기능을 만들겠습니다.

각각의 레시피에 조금 다른 부분이 있긴 하지만, 만드는 방법은 똑같다고 볼 수 있기 때문에, 다른 부분을 추상화 시켜 공유하기로 했습니다.

 

다른 부분을 찾아서 추상화를 했습니다.
이 클래스는 버거 레시피의 알고리즘 틀입니다.

 

알고리즘 틀

템플릿 메소드 패턴은 간단하게 생각하면 알고리즘의 틀을 만들기 위한 패턴입니다.
틀이란 일련의 단계들로 알고리즘을 정의한 메소드입니다.
일련의 단계들 중 하나 이상이 추상 메소드로 정의되며, 그 추상 메소드는 서브클래스에서 구현됩니다.
이렇게 하면 서브클래스에서 달라지는 부분을 구현할 수 있도록 하면서도 알고리즘의 구조는 바꾸지 않아도 됩니다.

 

달라지는 부분은 어떻게 구현할까요?

템플릿 메소드에서는 알고리즘의 각 단계들을 정의하며, 그 중 한 개 이상의 단계가 서브클래스에 의해  제공될 수 있습니다.

버거 레시피 추상클래스가 있고, 그 레시피를 상속받은 치킨버거와 불고기버거 클래스가 있습니다. 
서브클래스를 구현하여 다른 부분에 대해 정의하면 하겠습니다.

 

구현

BurgerBeverage

package cg.park.designpattern.templateMethod;

public abstract class BurgerBeverage {

    final void makeBurger() {
        menu();
        bread();
        patty();
        onion();
        sauce();
        cheese();
    }

    abstract void menu();//메뉴

    abstract void patty();//패티(돼지,닭,소 등...)

    abstract void sauce();//버거 소스

    void bread() { System.out.println("모닝빵");}

    void onion() { System.out.println("양파");}

    void cheese() { System.out.println("치즈");}

}

 

ChickenBurger

package cg.park.designpattern.templateMethod;

public class ChickenBurger extends BurgerBeverage {
    public void menu() { System.out.println("*치킨 버거");}
    public void patty() { System.out.println("치킨 패티");}
    public void sauce() { System.out.println("양념치킨소스");}
}

 

BulgogiBurger

package cg.park.designpattern.templateMethod;

public class BulgogiBurger extends BurgerBeverage {
    public void menu() { System.out.println("*불고기 버거");}
    public void patty() { System.out.println("불고기 패티");}
    public void sauce() { System.out.println("불고기 소스");}
}

 

TemplateMethodTest

package cg.park.designpattern.templateMethod;

public class TemplateMethodTest {
    public static void main(String[] args) {
        BulgogiBurger bulgogiBurger = new BulgogiBurger();
        ChickenBurger chickenBurger = new ChickenBurger();

        bulgogiBurger.makeBurger();
        System.out.println("==========================");
        chickenBurger.makeBurger();

    }
}

 

후기

템플릿 메서드 패턴은 실무에서도 많이 사용되는 패턴입니다.
디자인 패턴에 대해 학습하고, 신규로 접하는 프로젝트가 디자인 패턴이 적용되어 있다면 코드 확장에 필요한 지식이 될 것입니다.

 

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

스프링의 객체 생성

스프링의 가장 기본적인 기능은 객체를 생성하고 초기화하여 필요로 하는 곳에 제공하는 것인데, 이 중심에는 DI 설계 패턴이 적용되어 있습니다.

 

DI란 무엇일까요?

DI는 Dependency Injection의 약자로서, "의존성 주입"이라는 단어로 번역되어 사용됩니다.
DI는 의존을 처리하는 방법에 대한 내용입니다.
스프링은 기본적으로 DI를 기반으로 동작하기 때문에, 스프링을 제대로 사용하려면 DI에 대한 이해가 필수적입니다.

 

DI의 의존 처리

DI는 의존 객체를 외부에서 조립합니다.
의존 객체를 직접 생상하는 방식과 달리 DI(Dependency Injection)는 의존 객체를 외부로부터 전달받는 구현 방식입니다.
생성자를 이용해서 의존 객체를 전달받는 방식이 DI에 따라 구현한 것이기 때문입니다.

햄버거를 만드는 클래스가 있습니다.
치즈버거를 객체를 생성했습니다.
그럼 다른 버거는 어떻게 처리해야 할까요?
의존 객체를 내부에서 처리한다면 버거의 종류가 바뀔 때마다 클래스의 수정이 필요합니다.

public class BurgerStore {
    BurgerFactory factory = new CheeseBurger();//치즈버거 주문
    //BurgerFactory factory = new BulgogiBurger();//불고기버거 주문
    //BurgerFactory factory = new ChickenBurger();//치즈버거 주문
}

위의 상황처럼 매번 수정이 필요하다면 매우 불편할 것입니다.
저렇게 고정되어 있는 메뉴를 생성하는 것이 아닌 주문받은 메뉴를 생성하게 된다면 편리할 것입니다.

 

public class BurgerStore {
    BurgerFactory factory;

    public BurgerStore(BurgerFactory factory) {
        this.factory = factory;
    }
}

고정 되어있는 객체생성 방법이 아닌 주입받은 객체로 생성하는 방법입니다.
new CheeseBurger()로 선언되어있지 않고, 주입받는 Burger 객체로 생성이 됩니다.

 

내부에서 객체를 생성

 

외부에서 주입하여 객체 생성

 

후기

DI(Dependency Injection)는 스프링 특징 중 하나입니다.
스프링을 사용한다면 DI(Dependency Injection)는 필수로 알아야 합니다.
필수인 이유는 모든 개발자가 추구하는 결합도가 낮고, 응집도가 높은 구현 방법이기 때문입니다.
물론 DI(Dependency Injection)개념을 알고있다고 해서 모든것이 해결되는 것은 아닙니다.
그래도 스프링을 사용한다면 스프링의 특징과 사용법을 알면 더 나은 결과를 만들 수 있습니다.
감사합니다.

 

참조 자료: 웹 개발자를 위한 Spring 4.0 프로그래밍

참조 자료: 토비의 스프링 3.1 vol2

'스프링 > Spring' 카테고리의 다른 글

[Spring] POJO란 무엇인가?  (0) 2022.01.17
[스프링] 트랜잭션이란?  (0) 2022.01.12
[Spring] 스프링이란 무엇인가?  (0) 2022.01.10
[스프링] 기본 흐름과 주요 컴포넌트  (0) 2022.01.02

 

디자인패턴 Facade Pattern

퍼사드 클래스에서는 서브시스템 클래스들을 캡슐화하지 않습니다.
그냥 서브시스템의 기능을 사용할 수 있는 간단한 인터페이스를 제공할 뿐입니다.

 

Facade Pattern은 실제로 어떻게 적용할까요?

어떤 일을 하기 위해서는 작업 순서가 있습니다.
하지만 그 작업 순서를 하나씩 동작하기엔 너무 복잡하지 않을까요?
복잡한 작업 순서들을 하나로 묶어서 사용할 수 있게 된다면 편리하지 않을까요?
이런 경우에 퍼사드 패턴을 사용하면 됩니다.
퍼사드 클래스를 구현함으로써 복잡한 시스템을 훨씬 쉽게 사용할 수 있습니다.
퍼사드는 인터페이스를 단순화시킬 뿐 아니라 클라이언트와 구성요소들로 이루어진 서브시스템을 분리시키는 역할도 합니다.

 

세차하는 시스템을 퍼사드로 구현

세차를 할 때 작업 순서가 있습니다.
 -카 샴푸 사용
 -세차용 스펀지로 닦기
 -물통에 세차용 스펀지 세척하기
 -휠 브러시 사용하기
 -고압수 뿌리기
 -타월을 사용하여 물기 제거하기

 

 

이 작업 순서를 매번 하나씩 동작한다면 번거롭기도 하고, 복잡하기도 합니다.
더 간편한 방법이 있을지 찾아봅니다.
그래서 위의 작업 순서들을 하나로 묶기로 했습니다.

 

세차를 할 때 복잡하던 작업 순서들을 하나로 묶었습니다.
기존에는 각 클래스를 하나씩 동작해야 했다면, 이제는 CarWash라는 클래스로 기능들을 묶어서 한 번만 사용하면 됩니다.

 

구현

BubbleBomb

package cg.park.designpattern.facade;

public class BubbleBomb {
    String description;
    public BubbleBomb(String description) {
        this.description = description;
    }

    public void on() {System.out.println(description + " 샴푸 on");}

    public void off() {
        System.out.println(description + " 샴푸 off");
    }

}

 

WashMitt

package cg.park.designpattern.facade;

public class WashMitt {
    String description;

    public WashMitt(String description) {
        this.description = description;
    }

    public void on() {
        System.out.println(description + " 세차용 스폰지 on");
    }

    public void off() {
        System.out.println(description + " 세차용 스폰지 off");
    }

}

 

WheelBrush

package cg.park.designpattern.facade;

public class WheelBrush {
    String description;

    public WheelBrush(String description) {
        this.description = description;
    }

    public void on() {
        System.out.println(description + " 휠 브러시 on");
    }

    public void off() {
        System.out.println(description + " 휠 브러시 off");
    }

}

 

Bucket

package cg.park.designpattern.facade;

public class Bucket {
    String description;

    public Bucket(String description) {
        this.description = description;
    }

    public void on() {
        System.out.println(description + " 물통에 스폰지 세척 on");
    }

    public void off() {
        System.out.println(description + " 물통에 스폰지 세척 off");
    }
}


PressureWasher

package cg.park.designpattern.facade;

public class PressureWasher {
    String description;

    public PressureWasher(String description) {
        this.description = description;
    }

    public void on() {
        System.out.println(description + " 고압수 뿌리기 on");
    }

    public void off() {
        System.out.println(description + " 고압수 뿌리기 off");
    }

}

 

Towel

package cg.park.designpattern.facade;

public class Towel {
    String description;

    public Towel(String description) {
        this.description = description;
    }

    public void on() {
        System.out.println(description + " 타월로 물기 제거 on");
    }

    public void off() {
        System.out.println(description + " 타월로 물기 제거 off");
    }

}

 

CarWash

package cg.park.designpattern.facade;

public class CarWash {
    BubbleBomb bubbleBomb;//카 샴푸
    Bucket bucket;//물통
    PressureWasher pressureWasher;//고압 세척기
    Towel towel;//타월
    WashMitt washMitt;//세차용 스폰지
    WheelBrush wheelBrush;//휠 브러시

    public CarWash(
           BubbleBomb bubbleBomb
         , Bucket bucket
         , PressureWasher pressureWasher
         , Towel towel
         , WashMitt washMitt
         , WheelBrush wheelBrush) {

        this.bucket = bucket;
        this.washMitt = washMitt;
        this.wheelBrush = wheelBrush;
        this.bubbleBomb = bubbleBomb;
        this.towel = towel;
        this.pressureWasher = pressureWasher;
    }

    public void start() {
        bubbleBomb.on();//카 샴푸
        washMitt.on();//세차용 스폰지
        wheelBrush.on();//휠 브러시
        bucket.on();//물통
        pressureWasher.on();//고압수 뿌리기
        towel.on();//타월을 이용하여 물기 제거
    }

    public void end() {
        bubbleBomb.off();
        wheelBrush.off();
        washMitt.off();
        bucket.off();
        pressureWasher.off();
        towel.off();
    }

}

 

FacadeTest

package cg.park.designpattern.facade;

public class FacadeTest {

    public static void main(String[] args) {
        BubbleBomb bubbleBomb = new BubbleBomb("A사 제품");
        Bucket bucket = new Bucket("A사 제품");
        PressureWasher pressureWasher = new PressureWasher("A사 제품");
        Towel towel = new Towel("A사 제품");
        WashMitt washMitt = new WashMitt("A사 제품");
        WheelBrush wheelBrush = new WheelBrush("A사 제품");

        CarWash carWash = new CarWash(
                bubbleBomb
                , bucket
                , pressureWasher
                , towel
                , washMitt
                , wheelBrush);

        carWash.start();//세차 시작
        System.out.println("=================================");
        carWash.end();//세차 종료

    }

}

 

후기

처음에는 퍼사드패턴과 커맨드 패턴이 헷갈렸습니다.
퍼사드 패턴과 커맨드 패턴 둘 다 공부하니 개념적인 차이를 이해했습니다.
퍼사드 패턴: 서브클래스들을 캡슐화하지 않고 구현,  훨씬 쉽게 사용할 수 있는 인터페이스를 제공함으로써 복잡한 시스템을 쉽게 사용.
커맨드 패턴: 요구사항을 객체로 캡슐화, 매개변수로 다른 여러 가지 요구사항 추가 가능.

커맨드 패턴 보러가기

 

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

트랜잭션의 필요성

NoSQL과 빅데이터가 급부상하면서 RDBMS가 제공하는 엄격한 트랜잭션보다는 대용량 데이터 처리를 위한 느슨한 방식의 무결성 처리 기법을 적용하는 곳이 증가하고 있습니다.
하지만, 그럼에도 불구하고 전통적인 RDBMS가 제공하는 트랜잭션은 중요합니다.
예를 들어, 결제와 영화예매하는 시스템이 있습니다.
만약 이 시스템에서 결제는 이루어진 상태에서 영화 예매에 실패하는 상황이 발생하면 당연히 결제에 성공하면 안됩니다.
이런 경우 결제와 영화예매 모두 실패로 처리되어야 합니다.

 

트랜잭션이란?

트랜잭선은 여러 과정을 하나의 동작으로 묶을 때 사용됩니다.
트랜잭선 범위 내에 있는 처리 과정 중 한 가지라도 실패할 경우 전체 과정을 취소시킴으로써 데이터의 무결성을 보장합니다.
이러한 트랜잭션의 무결성 특징을 ACID라고 말합니다.
트랜잭션 과정 중 하나라도 실패한다면 모든 과정은 롤백됩니다.

 

롤백

데이터베이스에서 업데이트에 오류가 발생할 때, 이전 상태로 되돌리는 것을 말합니다.

 

ACID란 무엇일까요?

원자성(Atomicity)
트랜잭션은 한 개 이상의 동작을 논리적으로 한 개의 작업 단위로 묶습니다.
원자성은 트랜잭션 범위에 있는 동작이 모두 실행되거나 모두 실행되지 않음을 보장합니다.
모든 동작이 성공적으로 실행되면 트랜잭션은 성공입니다.
하나라도 실패한다면 트랜잭션은 실패하고 모든 과정은 롤백합니다.

 

일관성(Consistency)
트랜잭션은 항상 일괄적인 DB상태를 유지해야 합니다.
정의된 조건과 다른 결과가 나온다면 일관성이 지켜졌다고 말할 수 없습니다.
일관성이 지켜지지 않았다면 트랜잭션은 실패한 것이고, 모든 과정은 롤백합니다.

 

고립성(Isolation)
트랜잭션은다른 트랜잭션과 독립적으로 실행되어야 하며, 다른 트랜잭션이 동일한 데이터에 동시에 접근할 경우 접근을 제어해야 합니다.

 

지속성(Durability)
트랜잭션이 완료되면, 그 결과는 지속적으로 유지되어야 합니다.
DB에 반영된 데이터가 불특정하게 변경된다면, 지속성이 지켜졌다고 말할 수 없습니다.
이 경우에도 트랜잭션은 실패한 것이고, 모든 과정은 롤백합니다.

 

스프링의 트랜잭션 지원

스프링은 코드 기반의 트랜잭션 처리(Programmatic Transaction) 뿐 아니라 선언적 트랜잭션(Declarative Transaction)을 지원하고 있습니다.
개발자가 직접적으로 트랜잭션의 범위를 코드 수준에서 정의하고 싶은 경우에는 스프링이 제공하는 트랜잭션 템플릿 클래스를 이용해서 트랜잭션 범위를 지정할 수 있습니다.
또한, 설정 파일이나 어노테이션을 이용하여 트랜잭션 범위 및 규칙을 정의할 수 있기 때문에 트랜잭션을 매우 쉽게 관리할 수 있습니다.

 

트랜잭션 전파와 관련해서 스프링이 지원하는 속성

REQUIRED

메서드를 수행하는 데 트랜잭션이 필요하다는 것을 의미합니다.
현재 진행 중인 트랜잭션이 존재하면, 해당 트랜잭션을 사용합니다.
존재하지 않는다면 새로운 트랜잭션을 생성합니다.

 

MANDATORY

메서드를 수행하는 데 트랜잭션이 필요하다는 것을 의미합니다.
하지만, REQUIRED와 달리, 진행 중인 트랜잭션이 존재하지 않을 경우 익셉션을 발생시킵니다.

 

REQUIRES_NEW

항상 새로운 트랜잭션을 시작합니다.
기존 트랜잭션이 존재하면 기존 트랜잭션을 일시 중지하고 새로운 트랜잭션을 시작합니다.
새로 시작된 트랜잭션이 종료된 뒤에 기존 트랜잭션이 계속됩니다.

 

SUPPORTS

메서드가 트랜잭션을 필요로 하지 않지만, 기존 트랜잭션이 존재할 경우 트랜잭션을 사용한다는 것을 의미합니다.
진행 중인 트랜잭션이 존재하지 않더라도 메서드는 정상적으로 동작합니다.

 

NOT_SUPPORTED

메서드가 트랜잭션을 필요로 하지 않음을 의미합니다.
SUPPORTS와 달리 진행 중인 트랜잭션이 존재할 경우 메서드가 실행되는 동안 트랜잭션은 일시 중지되며, 메서드 실행이 종료된 후에 트랜잭션을 계속 진행합니다.

 

NEVER

메서드가 트랜잭션을 필요로 하지 않으며, 만약 진행 중인 트랜잭션이 존재하면 익셉션을 발생시킵니다.

 

NESTED

기존 트랜잭션이 존재하면, 기존 트랜잭션에 중첩된 트랜잭션에서 메서드를 실행합니다.
기존 트랜잭션이 존재하지 않으면 REQUIRED와 동일하게 동작합니다.
이 기능은 JDBC 3.0 드라이버를 사용할 때에만 적용됩니다.

 

트랜잭션 격리 레벨

DEFAULT

기본 설정을 사용합니다.

 

READ_UNCOMMITTED

다른 트랜잭션에서 커밋하지 않은 데이터를 읽을 수 있습니다.

 

READ_COMMITTED

다른 트랜잭션에 의해 커밋된 데이터를 읽을 수 있습니다.

 

REPEATABLE_READ

처음에 읽어 온 데이터와 두 번째 읽어 온 데이터가 동일한 값을 갖습니다.

 

SERIALIZABLE

동일한 데이터에 대해서 동시에 두 개 이상의 트랜잭션이 수행될 수 없습니다.

 

후기

스프링의 트랜잭션은 개념 뿐 아니라 내부적으로 어떻게 돌아가는지 공부할 필요가 있습니다.

 

참조 자료: 웹 개발자를 위한 Spring 4.0 프로그래밍

 

디자인패턴 Adapter Pattern

클래스의 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환합니다.
인터페이스가 호환되지 않아 쓸 수 없었던 클래스들을 같이 사용할 수 있게 해줍니다.

 

어댑터란?

어댑터는 그리 어렵지 않게 이해할 수 있습니다.
우리 주변에서 어댑터를 쉽게 볼 수 있습니다.
예를 들어, 한국에서 쓰던 220볼트 어댑터를 해외에 가서 사용하려면 어떻게 해야할까요?
같은 220볼트를 사용하는 국가라면 괜찮지만, 그렇지 않다면?

다들 어댑터가 어떻게 사용되고 있는지 알고 있습니다.
다른 부분을 변환하여 연결시키는 중간역할입니다.

 

 

어댑터 패턴은 실제로 어떻게 적용할까요?

기존 시스템과 신규 시스템이 호환되지 않는다면?

 

어댑터의 역할을 여기서 확인할 수 있습니다.
그림만 봐도 느낌이 오지 않나요?

 

어댑터가 있다면 다른 부분을 변환하여 연결시키는 중간역할을 할 수 있습니다.

 

구현

한국에서 사용하는 220볼트 플러그와 일본에서 사용하는 110볼트 플러그가 있습니다.
220볼트를 어댑터를 사용하여 110볼트를 사용할 수 있게 만들고,
110볼트도 어댑터를 사용하여 220볼트를 사용할 수 있게 만들 예정입니다.

 

Volt220

package cg.park.designpattern.adapter;

public interface Volt220 {
    public void plugIn220();
}

 

usedInKorea

package cg.park.designpattern.adapter;

public class usedInKorea implements Volt220 {
    public void plugIn220() { System.out.println("220볼트 사용");}
}

 

Volt110

package cg.park.designpattern.adapter;

public interface Volt110 {
    public void plugIn110();
}

 

usedInJapan

package cg.park.designpattern.adapter;

public class usedInJapan implements Volt110 {
    public void plugIn110() { System.out.println("110볼트 사용");}
}

 

VoltAdapter220

package cg.park.designpattern.adapter;

public class VoltAdapter220 implements Volt110 {
    Volt220 volt220;

    public VoltAdapter220(Volt220 volt220) {
        this.volt220 = volt220;
    }

    public void plugIn110() {
        volt220.plugIn220();
    }
}

 

VoltAdapter110

package cg.park.designpattern.adapter;

public class VoltAdapter110  implements Volt220 {
    Volt110 volt110;

    public VoltAdapter110(Volt110 volt110) {
        this.volt110 = volt110;
    }

    public void plugIn220() {
        volt110.plugIn110();
    }
}

 

AdapterTest

package cg.park.designpattern.adapter;

public class AdapterTest {
    public static void main(String[] args) {
        Volt110 volt110 = new VoltAdapter220(new usedInKorea());
        Volt220 volt220 = new VoltAdapter110(new usedInJapan());

        volt110.plugIn110();//volt110 사용
        volt220.plugIn220();//volt220 사용

    }

}

 

110볼트를 220볼트 어댑터로 감싸고, 

220볼트를 110볼트 어댑터로 감싼 후

실행하게 되면 위와 같이 결과가 나옵니다.

220어댑터(110볼트): 220볼트 사용

110어댑터(220볼트): 110볼트 사용

 

후기

어댑터의 개념은 많이 사용된다.

코드를 고치지 않고 새로운 라이브러리를 적용할 수 있다는 것은 큰 장점이다.

 

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

Microsoft REST API Guidelines(2016)

Microsoft에서 2016년에 REST API Guidelines를 발표.
URI는 https://{serviceRoot}/{CCollection}/{id} 형식이어야 함.
GET, PUT, DELETE, POST, HEAD, PATCH, OPTIONS를 지원해야 함.
API 버저닝은 Major, minor로 하고 URI에 버전 정보를 포함시킴.

하지만 Roy Fielding는 "이건 REST API가 아니다. HTTP API다."라고 말을 했습니다..

 

그럼 뭐가 문제일까요?

REST API란 REST 아키텍처를 준수해야 합니다.
REST는 분산 하이퍼미디어 시스템을 위한 아키텍처 스타일입니다.
아키텍처 스타일이란 제약조건의 집합입니다.

 

REST를 구성하는 스타일은 뭐가 있을까요?

client-server
 -아키텍처를 단순화시키고 작은 단위로 분리(decouple)함으로써 클라이언트-서버의 각 파트가 독립적으로 개선될 수 있도록 해줍니다.

stateless 
 -클라이언트와 서버 관계에서 서버가 클라이언트의 상태를 보존하지 않음을 의미한다. HTTP는 stateless를 기본적으로 가지고 있다.

cache
 -WWW에서와 같이 클라이언트는 응답을 캐싱할 수 있어야 합니다.

layered system
 -클라이언트는 대상 서버에 직접 연결되었는지, 또는 미들웨어에 연결되었는지를 알 수 없어야 합니다.

code-on-demand(optional)
 -서버로부터 코드를 받아 실행할 수 있는 것입니다. (자바스크립트)

위의 REST 스타일들은 잘 지켜지는데, 그렇지 않은 스타일이 1가지 있습니다. 
uniform interface입니다.

 

uniform interface란?

identification of resources
 -리소스가 uri로 식별되어야 한다.
manipulation of resources through representations
 -리소스를 만들거나 업데이트나 삭제할 때 HTTP 메시지에 표현을 담아 전송해서 달성한다.
self-descriptive message
 -메시지는 스스로 설명해야 한다.
HATEOAS
 -애플리케이션 상태의 전이입니다.

uniform interface 중 self-descriptivemessage와 HATEOAS가 잘 지켜지지 않습니다.

 

self-descriptive message란?

요청

요청을 보낼 때 이런 형식으로만 보내면 안 됩니다.

 

목적지가 있어야 self-descriptive입니다.

 

응답

위의 내용은 Content-Type이 application/json이라는 것 까지는 알 수 있지만, 응답 내용으로 받은 op와 path가 어떤 데이터인지 알 수 없습니다.

 

위의 내용처럼 application/hson-patch+json 까지 받아야 json patch의 명세를 찾아가서 이해 후 메시지를 올바르게 해석 가능합니다.
오늘날은 application/json 까지만 정의되어 있는 경우가 대다수입니다.
REST하지 못하다고 할 수 있습니다.

 

HATEOAS란?

 

 

HTML A태그를 통해 하이퍼링크가 나와있고 하이퍼링크를 통해서 다음 상태로 전이 가능해야 합니다.

 

WHY?

독립적 진화를 위해.
서버와 클라이언트가 각각 독립적으로 진화해야 함.
서버의 기능이 변경되어도 클라이언트를 업데이트할 필요가 없음.
HTTP를 고치면 웹이 깨질 것 같은데 어떻게 할지 고민 끝에 나온 것이 REST.
REST는 독립적인 진화가 목적.

 

독립적인 진화란?

uniform interface를 반드시 만족.
uniform interface를 만족하지 못하면 REST가 아님.

 

REST가 웹의 독립적 진화에 도움을 주었나?

HTTP에 지속적으로 영향을 줌.
HOST 헤더 추가.
길이 제한을 다루는 방법 명시(414 URI Too Long 등)
URI에서 리소스의 정의가 추상 적로 변경됨(식별하고자 하는 무언가)
기타 HTTP와 URI에 많은 영향을 줌.
HTTP/1.1 명세 최신판에서 REST에 대한 언급이 들어감.
Reminder: Roy T. Fielding이 HTTP와 URI명세의 저자 중 한 명.

 

그럼 REST는 성공했는가?

REST는 웹의 독립적 진화를 위해 만들어졌음.
웹은 독립적으로 진화하고 있음.
성공이라고 봄.

 

REST API는?

REST API는 REST 아키텍처 스타일을 따라야 합니다.
오늘날 스스로 REST API라고 하는 API들은 대부분이 REST 아키텍처 스타일을 따르지 않습니다.

 

REST API 제약조건을 모두 지켜야 하는가?

Roy Fielding가 "하이퍼텍스트를 포함한 self-descriptive 한 메시지의 uniform interface를 통해 리소스에 접근하는 API"라고 선언했습니다.
REST API 제약조건을 지키지 않는다면 REST API가 아닙니다.

 

API는 꼭 REST API여야 하는가?

Roy T.Fielding는 "시스템 전체를 통제할 수 있다고 생각하거나, 진화에 관심이 없다면, REST에 대해 따지느라 시간을 낭비하지 마라."라고 말을 했습니다.

 

현재

REST API를 구현하고 REST API라고 부른다.
REST API 구현을 포기하고 HTTP API라고 부른다.
REST API가 아니지만 REST API라고 부른다.(현재 상태)
Roy T.Fielding은 "제발 제약 조건을 따르던지 아니면 다른 단어를 써라."라는 말을 했습니다.

 

후기

현재 사용하고 있는 API가 REST API인지 다시 생각해봐야 합니다.

 

참조 자료: 유튜브 그런 REST API로 괜찮은가?

+ Recent posts