SSARTEL-10th / JPTS_bookstudy

"개발자가 반드시 알아야 할 자바 성능 튜닝 이야기" 완전 정복
7 stars 0 forks source link

일상적으로 사용되는 패턴에는 어떤 것이 있을까? #2

Open kgh2120 opened 10 months ago

kgh2120 commented 10 months ago

👍 문제

소프트웨어 디자인 패턴이란 '특정 문맥에서 공통적으로 발생하는 문제에 대해 재사용 가능한 해결책이다.' 라고 합니다. 우리가 일반적으로 사용하는 패턴 혹은 사용하는 프레임워크에 적용된 패턴은 어떤 것이 있고, 어떤 장점이 있을까요?

✈️ 선정 배경

story1 을 읽는데, 처음 보는 패턴들도 많았고, DAO나 TO와 같이 패턴인지도 모르고 사용하는 경우들도 많았습니다. 또한 사용하는 프레임워크 역시 많은 패턴이 적용되어있는데, 이번 기회에 공부하고 정리하면 도움이 될 것 같아서 선정했습니다.

📺 관련 챕터 및 레퍼런스

story1. 디자인 패턴, 꼭 써야 한다

🐳 비고

전부 찾아보면 끝도 없으니까, 3~5개 정도만 준비해볼까요??

ChoiSeEun commented 10 months ago

서론

디자인 패턴 : 소프트웨어 디자인 과정에서 자주 발생하는 문제들에 대한 일반적인 해결책 https://refactoring.guru/ko/design-patterns

"바퀴를 다시 발명하지 마라"

디자인 패턴은 종류가 굉장히 다양하므로, 모든 디자인 패턴을 모두 정리하기에는 무리가 있다. 따라서, 교재에서 적어도 공부해야 한다고 언급된 패턴들과 Spring Framework에서 주로 언급되는 패턴들만 간단하게 다루고자 한다.

💡 Business Delegate

특정 케이스마다 처리해야 하는 로직이 다른 경우에 사용 되는 패턴

해당 패턴에서 클라이언트는 delegate 객체만 호출해서 원하는 일을 시키면 된다. delegate 객체는 내부적으로 lookup 객체를 호출해서 특정 케이스에 맞는 실제 구현체를 반환한다.

① 구조

Business Service

public class CircleDrawService implements DrawService {

@Override public void draw() { System.out.println("Drawing circle"); } }

### Lookup Service
- `delegate`가 호출하는 클래스
- 특정 케이스마다 처리를 담당하는 실제 구현체를 반환
```java
public class DrawServiceLookUp {
   public DrawService getDrawService(String drawType){

      if(drawType.equalsIgnoreCase("CIRCLE")){
         return new CircleDrawService();
      }
      else {
         return new RectangleDrawService();
      }
   }
}

Business Delegate

💡 Dependency Injection

외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴 : 클래스 레벨에서 의존관계가 고정되지 않도록 하고 런타임 시에 관계를 동적으로 주입

EJB는 객체 지향적이지 않고, 비즈니스 로직에 특정 기술이 종속된다는 문제점이 존재한다. 이 문제점을 해결하기 위해 Spring은 특정 기술에 종속되지 않고 객체를 관리할 수 있는 컨테이너를 제공하는데, 그 대표적인 방법이 Dependency Injection이다.

주로 클래스에 대한 의존성의 인터페이스화를 통해 코드 유연성을 증대하거나, 클래스의 인스턴스를 외부에서 생성하여 주입하는 방식으로 이해하면 된다.

public class BeanFactory {

    public void store() {
        // Bean의 생성 : 어플리케이션 실행 시점에 필요한 객체(빈)를 생성 
        Product pencil = new Pencil();

        // 의존성 주입 : 의존성 있는 두 객체를 연결하기 위해 한 객체를 다른 객체로 주입
        Store store = new Store(pencil);
    }   
}

① 장점

② Spring에서 의존성을 주입하는 방법

1) Field Dependency Injection

멤버 필드에 @Autowired 을 선언하여 주입 받는 방법


@Controller
public class TestController {
@Autowired
TestService testService;

}

가장 코드가 단순하지만, 프레임워크에 의존적이라는 단점이 존재한다. 즉, 스프링 DI 컨테이너에서만 동작하며 외부에서 수정이 불가능해서 테스트가 어렵다. 또한 `final` 선언이 불가능하므로 객체가 변경될 수 있다는 점도 단점이다.

### 2) Setter Dependency Injection
> setter 메소드에 @Autowired 을 선언하여 주입 받는 방법
```java
@Controller
public class TestController {

    private TestService testService;

    @Autowired
    public void setTestService(TestService testService) {
        this.testService = testService;
    }

}

스프링 3.x대 버전에서 추천되는 방식이고, 현재는 주입 받는 객체가 변경될 가능성이 있는 경우에만 사용되는 방식이다. 생성자에 모든 의존성을 기술하면 복잡해질 수 있는 것을 선택적으로 나눠서 주입할 수 있는 장점이 있다. 하지만 여전히 의존성 주입 대상 필드는 final선언이 불가능하다.

3) Constructor Injection

생성자에 의존성 주입을 받고자 하는 필드를 나열하는 방법


@Controller
public class TestController {
private final TestService testService;

@Autowired // 생성자가 1개만 있을 경우 생략 가능
public TestController(TestService testService) {
    this.testService = testService;
}

}

// 간결한 방식 @Controller @RequiredArgsConstructor public class TestController {

private final TestService testService;

}

이 방식이 현재 스프링 프레임워크에서 가장 권장되는 방식이다. 프레임워크에 의존적이지 않아 테스트가 용이하고, `final`선언이 가능하여 유지 보수가 용이하기 때문이다. 또한 순환 참조 에러를 컴파일 시에 판단할 수 있는 장점도 있다. 

# 💡 Session Facade
> 비즈니스 티어 컴포넌트를 캡슐화하고 원격 클라이언트에서 접근할 수 있는 서비스를 제공 

주로 **Facade Pattern**으로 불리며, 서브 시스템 내부에 있는 클래스에 접근이 가능한 하나의 통합된 인터페이스를 제공하는 패턴을 의미한다. 클라이언트가 비즈니스 구성 요소에 직접 액세스할 수 없고, 하지 않아도 되는 것에 의미가 있다. 
```java
// 컴퓨터 내부 부품
class CPU {
    public void freeze(){}
    public void jump(long position){}
    public void exectute(){}
}

class Memory{
    public void load(long position,byte[] data){}
}

class HardDrive{
    public byte[] read(long lba,int size){}
}

// Facade
class Computer{
    public void startComputer(){
        Cpu cpu = new CPU();
        Memory memory = new Memory();
        HardDrive hardDrive = new HardDrive();
        cpu.freeze();
        memory.load(BOOT_ADDRESS,hardDrive.read(BOOT_SECTOR,SECTOR_SIZE));
        cpu.jump(BOOT_ADDRESS);
        cpu.execute();
    }
}

// client
class User{
    public static void main(String[] args) throws ParseException{
        Computer facade = /* grab a facade instance * /;
        facade.startComputer();
    }
}

💡 Data Access Object

DB 접근을 전담하는 클래스를 추상화하고 캡슐화하는 패턴

고수준의 Business Logic과 저수준의 Persistence Logic을 분리하기 위한 목적으로 사용되는 패턴이다. Persistence Logic을 캡슐화하고 도메인 레이어에 객체지향적인 인터페이스를 제공함으로써, 어댑터 역할을 수행한다. 따라서 DB가 변경되더라도 클라이언트 로직은 변경되지 않도록 한다.

// 우아한테크코스 레벨2 스프링 체스게임 미션 일부분

// Persistence Layer에 해당하는 클래스
// 직접 SQL 문으로 데이터베이스에 접근하는 로직이 존재 
@Repository
public class JdbcRoomDao implements RoomDao {
    // ...

    @Override
    public void createRoom(Room room) {
        String query = String.format("INSERT INTO %s VALUES (?, ?, ?, 'WHITE')", TABLE_NAME);
        jdbcTemplate.update(query, room.getId().getValue(), room.getRoomTitle().getValue(),
                room.getPassword().getValue());
    }

// Business Layer에 해당하는 클래스
// 비즈니스 로직을 수행하기 위해 JdbcRoomDao 의 메소드를 호출 

@Service
public class ChessService {
    // ...

    public RoomDto createRoom(CreateRoomDto createRoomDto) {
        Room room = Room.create(createRoomDto.getTitle(), createRoomDto.getPassword());
        roomDao.createRoom(room);

        initializeBoard(room);

        return RoomDto.from(room);
    }

💡 Singleton

애플리케이션 당 오직 하나의 인스턴스만 존재하도록 보장해주는 패턴

Spring 에서는 하나의 Spring IoC Container 당 하나의 Singleton Object를 갖도록 제한한다. 따라서 여러 Container를 가진 애플리케이션이라면 같은 클래스의 객체가 여러 개 존재할 수 있기 때문에, 엄밀히 따지면 일반적인 Singleton 정의와는 다르다고 볼 수 있다. Spring은 기본적으로 모든 Bean들을 Singleton으로 생성하므로, 단일 애플리케이션 내에서 두 Controller를 생성하고 같은 타입의 bean을 각각 주입하는 것이 가능하다.

@RestController
public class LibraryController {

    @Autowired
    private BookRepository repository;

    @GetMapping("/count")
    public Long findCount() {
        System.out.println(repository);
        return repository.count();
    }
}
@RestController
public class BookController {

    @Autowired
    private BookRepository repository;

    @GetMapping("/book/{id}")
    public Book findById(@PathVariable long id) {
        System.out.println(repository);
        return repository.findById(id).get();
    }
}

Application을 실행하고 두 요청을 실행하면 repository 객체는 같은 Object ID를 가진다. 즉, LibraryController와 BookController에 같은 BookRepository bean을 주입했다는 것을 알 수 있다.

💡 Factory Method

원하는 객체를 생성하기 위한 추상 메서드가 있는 팩토리 클래스를 생성

일반화된 예시로 Vehicle 객체를 생각해볼 수 있다. 해상 환경에서는 Boat 객체를 만들고, 항공 환경에서는 Airplain 객체를 만들고 싶은 경우 Factory Method를 적용할 수 있다.

Spring 에서는 이 기술을 Dependency Injection에서 사용한다. 기본적으로 Spring 은 Bean Container를 Bean을 생성하는 Factory로 취급한다. 따라서 BeanFactory 인터페이스를 Bean Container의 추상화로 정의한다.

public interface BeanFactory {

    getBean(Class<T> requiredType);
    getBean(Class<T> requiredType, Object... args);
    getBean(String name);

    // ...
}

위의 코드에서 getBean() 은 팩토리 메서드로 간주되어, 메서드에 제공된 기준과 일치하는 Bean을 반환한다.

💡 Proxy

한 객체(proxy)가 다른 객체(subject or service)로의 접근을 제어하도록 하는 기술

대표적인 예시가 Transaction에서 Proxy 패턴을 볼 수 있다. 또한 일반적으로 Spring에서는 사용하는 Proxy에는 CGLib ProxyJDK Dynamic Proxy가 있다. JDK Dynamic Proxy CGLib Proxy
인터페이스를 구현한 클래스에 대한 프록시를 생성 Class Generation Library
대상 객체가 반드시 인터페이스를 구현해야 하는 제한이 존재 인터페이스를 구현하지 않은 클래스에도 적용할 수 있음
자바에서 기본적으로 지원하기 때문에 가장 일반적으로 사용됨 대상 클래스가 final로 선언되어 있으면 안됨
@Transactional 또는 Spring AOP를 사용하여 트랜잭션 프록시를 생성하고 트랜잭션을 관리 대상 객체를 상속받는 서브클래스를 생성하여, 대상 객체의 메서드에 대한 호출을 오버라이딩하여 트랜잭션 관리를 수행

💡 Template

공통된 작업 흐름을 정의하고, 이를 서브 클래스에서 구체적으로 구현할 수 있게 해주는 패턴

해당 패턴은 코드 재사용과 일관성을 유지하는데 도움을 준다.Spring에서 Template 패턴을 구현하는 대표적인 클래스는 JdbcTemplate이 있다. 아래의 코드의 MyDao 클래스는 데이터베이스 액세스 작업을 수행하는 메서드를 정의한다. SQL 쿼리를 실행하기 위해서 JdbcTemplate을 사용하는데, 데이터베이스 액세스의 공통된 부분은 템플릿으로 정의하고 구체적인 데이터베이스 액세스 로직은 서브 클래스에서 구현할 수 있도록 한다.

public class MyDao {
    private JdbcTemplate jdbcTemplate;

    public MyDao(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void saveData(String data) {
        // 데이터 저장 작업의 템플릿 코드
        String sql = "INSERT INTO my_table (data_column) VALUES (?)";
        jdbcTemplate.update(sql, data);
    }

    public String fetchDataById(int id) {
        // 데이터 조회 작업의 템플릿 코드
        String sql = "SELECT data_column FROM my_table WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, String.class, id);
    }
}

참고자료

https://jaepils.github.io/pattern/2018/07/24/pattern-delegate.html https://kim-oriental.tistory.com/32 https://mangkyu.tistory.com/150 https://rlawls1991.tistory.com/entry/Session-Facade-%ED%8C%A8%ED%84%B4 https://hudi.blog/data-access-object/ https://loginfo.tistory.com/2

kgh2120 commented 10 months ago

고생하셨습니다!

제가 생각한 몇 개를 더하자면 레이어드 아키텍쳐 패턴과 패턴이라고 불리지는 않지만 소프트웨어 전략인 pooling과 caching이 있습니다.

Layered architecture pattern

레이어드 아키텍쳐 패턴은 Spring에서 개발을 하시다보면 패키지 구조를 controller, service, repository와 같은 형태로 나누어서 개발을 하는 경우가 많은데, 이 경우에 적용이 된 것입니다.

각 계층에 맞는 코드만을 작성하기 때문에 유지 보수성이 뛰어나지만, 코드를 짜는데 불편함이 존재한다는 단점이 있습니다.

image

Pooling

Pooling은 Spring에서 Connection Pool, Thread Pool과 같은 형태로 적용이 되어 있습니다. 매번 Connection, Thread를 만들지 않고 만들어 둔 객체를 재사용해 성능을 향상시키는 전략입니다.

캐싱은 다들 아실만 하니까... ㅎㅎ