SMALL

Observer Pattern

옵저버 패턴이란

객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여
상태 변화가 있을 때마다 옵저버들에게 통지하도록 하는 디자인 패턴입니다.

 > 어떤 객체의 변경 사항이 발생하였을 때 이와 연관된 객체들에게 알려주는 디자인 패턴이다.

 

 

 

옵저버 패턴에는 주체 객체와 상태의 변경을 알아야 하는 관찰 객체(Observer Object)가 존재하며 

이들의 관계는 보통 1:N이다. ( 1:1도 가능 ) 

 

Observer Pattern 특징

  • Loose Coupling : 상호작용을 하긴 하지만, 서로에 대해 잘 모른다
                                객체 간의 의존성을 제거할 수 있다

 

객체 간의 의존성을 줄이기 위해서 interface를 정의한다 ( Subject, Observer ) 

public interface Subject {

	public void registerObserver(Observer o);

	public void removeObserver(Observer o);

	public void notifyObserver();
}

public interface Observer {

	public void update(float tmp, float humidity, float pressure);
	
}

public interface DisplayElement {
	public void display();
}

 

Subject의 구현체, Observer List를 가지고 있다

public class WeatherData implements Subject {

	private List<Observer> observers;
	private float temperature;
	private float humidity;
	private float pressure;

	public WeatherData() {
		observers = new ArrayList<Observer>();
	}

	@Override
	public void registerObserver(Observer o) {
		observers.add(o);
	}

	@Override
	public void removeObserver(Observer o) {
		int idx = observers.indexOf(o);

		if (idx >= 0) {
			observers.remove(idx);
		}
	}

	@Override
	public void notifyObserver() {
		for (Observer observer : observers) {
			observer.update(temperature, humidity, pressure);
		}
	}
	
	public void measurementsChanged() {
		notifyObserver();
	}
	
	public void setMeasurements(float temperature, float humidity, float pressure) {
		this.temperature = temperature;
		this.humidity = humidity;
		this.pressure = pressure;
		
		measurementsChanged();
	}

}

 

Observer의 구현체, subject를 가지고 있고, 그를 통해 자신을 등록한다 

public class CurrentConditionsDisplay implements Observer, DisplayElement {

	private float temperature;
	private float humidity;
	private WeatherData weatherData;

	public CurrentConditionsDisplay(WeatherData weatherData) {
		this.weatherData = weatherData;
		this.weatherData.registerObserver(this);
	}

	@Override
	public void display() {
		System.out.println("Current Condition : " + temperature + " F degrees , " 
        						+ humidity + " % humidity");

	}

	@Override
	public void update(float tmp, float humidity, float pressure) {
		this.temperature = tmp;
		this.humidity = humidity;

		display();
	}

}

 

수행 코드 

	public static void main(String[] args) {
		WeatherData weatherData = new WeatherData();
		
		CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
		
		weatherData.setMeasurements(80, 65, 30.4f);
		weatherData.setMeasurements(82, 70, 29.2f);
		weatherData.setMeasurements(78, 90, 28.4f);
	}

 

Java 개발자라면, java.util에서 제공하는 Observer 패턴 관련 코드를 이용할 수 있다.

다만, Subject에 해당하는 Observable은 인터페이스가 아니고 상속받아야 해서 사용하는데 제한이 있을 수 있다.

import java.util.Observable;
import java.util.Observer;

 

 

 

 

LIST
SMALL

Strategy Pattern

객체들이 할 수 있는 행위 각각에 대해 전략 클래스를 생성하고, 유사한 행위들을 캡슐화하는 인터페이스를 정의하여, 객체의 행위를 동적으로 바꾸고 싶은 경우 직접 행위를 수정하지 않고 전략을 바꿔주기만 함으로써 행위를 유연하게 확장하는 방법을 말합니다.

 

간단히 말해서 

객체가 할 수 있는 행위들 각각을 전략으로 만들어 놓고, 동적으로 행위의 수정이 필요한 경우 전략을 바꾸는 것만으로 행위의 수정이 가능하도록 만든 패턴입니다.

 

 

객체지향 원칙

  • 바뀌는 부분은 캡슐화한다
  • 상속보다는 구성을 활용한다
  • 구현이 아닌 인터페이스에 맞춰서 프로그래밍한다

전략 패턴에서는 위의 원칙들을 포함하는 구조가 가능하다.

 

public abstract class Duck {
	
	FlyBehavior flyBehavior;
	QuackBehavior quackBehavior;
	
	public Duck() {
		
	}
	
	public void performFly() {
		flyBehavior.fly();
	}
	
	public void performQuack() {
		quackBehavior.quack();
	}
	
	public abstract void display();
	
	public void swim() {
		System.out.println("s w i m ~ ~ ");
	}
	
	public void setFlyBehavior(FlyBehavior fb) {
		flyBehavior = fb;
	}
	
	public void setQuackBehavior(QuackBehavior qb) {
		quackBehavior = qb;
	}
	
}

Duck에서는 FlyBehavior, QuackBehavior에 해당하는 전략을 가지고 있다.

 

public interface FlyBehavior {
	public void fly();
}

public interface QuackBehavior {
	public void quack();
}

 

그리고 마지막으로, Duck과 전략들의 구현체들을 보자

public class FlyNoWay implements FlyBehavior {
	@Override
	public void fly() {
		System.out.println(" I cannot fly ~ ");
	}
}

public class FlyWithWings implements FlyBehavior {
	@Override
	public void fly() {
		System.out.println(" fly in the moon ~ ");
	}
}

public class Quack implements QuackBehavior {
	@Override
	public void quack() {
		System.out.println("Quack ~~ Quack ~");
	}
}

public class Squeak implements QuackBehavior{
	@Override
	public void quack() {
		System.out.println("Squeak ~~ squeak ~ ");
	}
}

 

public class MallardDuck extends Duck {

	public MallardDuck() {
		quackBehavior = new Quack();
		flyBehavior = new FlyWithWings();
	}
	
	@Override
	public void display() {
		System.out.println("I'm MallarDuck~~ ~");
	}

}
LIST
SMALL

Mybatis란

Mybatis는 Psersistence Framework 중에 하나로 SQL Mapper Framework에 해당한다.

https://joomn11.tistory.com/34

위의 글에서 해당 내용에 대한 정리를 해두었으니 참고하면 이해가 잘 될 것이다.

 

Mybatis 특징

  • 복잡한 쿼리나 다이나믹 쿼리에 유용하다
  • 비즈니스 코드와 SQL 쿼리를 분리해서 관리 가능하다 ( 간결성, 코드 유지 보수성 향상 )

 

SpringBoot + Mybatis 

spring-boot의 mybatis-spring-boot-starter를 활용하여 스프링 프로젝트에서 손쉽게 Mybatis를 사용할 수 있다. 

 

mybatis-spring-boot-starter의 장점

  • spring에서 추가된 mybatis bean 등록을 자동으로 해준다
    • SqlSessionFactoryBean, SqlSessionTemplate

 

 

pom.xml

		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.2.2</version>
		</dependency>

 

mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sample.dao.MapperDAO">
	<select id="selectAll"
		resultType="com.sample.dto.UserDto">
		SELECT *
		FROM user_test
	</select>
	<insert id="insert" parameterType="com.sample.dto.UserDto">
		INSERT INTO user_test (seq, name, country) values ( #{seq}, #{name},
		#{country})
	</insert>
</mapper>

 

Mapper interface

@Repository
@Mapper
public interface MapperDAO {
    List<UserDto> selectAll() throws Exception;
    void insert(UserDto user);
}

 

Service 

@Service
public class UserService {

    @Autowired
    private MapperDAO mapperDao;
    
    public List<UserDto> selectAll() throws Exception {
        return mapperDao.selectAll();
    }
    
    public void insert(UserDto user) {
        mapperDao.insert(user);
    }
}

 

application.yml 설정

mybatis:
  mapper-locations:
  - com/sample/mapper/*.xml
  config-location: classpath:mybatis-config.xml
  • mybatis.mapper-locations : mapper관련 xml 파일이 존재하는 디렉터리의 위치
  • mybatis.config-location : mapper configuration관련 xml을 사용한다면 해당 위치에 대한 정보

 

mybatis github sample

https://github.com/mybatis/spring/tree/master/src/test/java/org/mybatis/spring/sample

 

mybatis사이트 가이드

http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/#

 

LIST
SMALL

Spring Cloud Netflix Eureka

MSA 환경에서는 Service의 Ip, Port 정보가 일정하지 않고 지속적으로 변화할 수 있다.

이러한 환경에서 Service의 정보를 수동으로 입력하고 관리하는 것은 한계가 분명하다.

이를 Service Discovery를 통해 해결할 수 있다.

 

Eureka의 구성 요소

Service Discovery

  • 외부에서 마이크로 서비스의 위치를 찾아주기 위한 기능

Service Registry

  • 각각의 서비스가 자신의 위치(Ip, Port) 정보를 특정 버서에 등록(Registry)하는 작업

 

Eureka 사용 예시

기존에 작성한 포스팅에서 API Gateway를 사용하였다.

(https://joomn11.tistory.com/123)

 

해당 설정에서 API Gateway의 설정에 각각의 서비스들의 물리적인 정보가 그대로 적혀있다.

이러한 경우 서비스의 물리적 정보가 변경되는 경우 API Gateway도 변경되어야 하는 디펜던시가 생기게 된다.

이러한 상황을 방지하기 위해 Service Discovery를 사용해보자.

 

Eureka Server 

 

dependency 추가 (build.gradle)

dependencies {
	implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
}

 

application.yml 설정

server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    service-url:
      default-zone: http://${eureka.instance.hostname}:${server.port}/eureka
    fetch-registry: false
  instance:
    hostname: localhost

 

Annotation 추가

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(EurekaServerApplication.class, args);
	}

}

 

Eureka Client

 

dependency 추가 (build.gradle)

dependencies {
	implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
}

 

application.yml 설정

eureka:
  instance:
    appname: product-service
  client:
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka

 

Annotation 추가

@SpringBootApplication
@EnableEurekaClient
public class ProductServiceApplication {

	public static void main(String[] args) {
		SpringApplication.run(ProductServiceApplication.class, args);
	}

}

 

API Gateway

위와 같이 Eureka Server, Client를 설정해두면, API Gateway의 설정 정보 중에 물리적인 주소를 하드 코딩한 부분을 수정해 줄 수 있다.

 

spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes:
        - id: product-service
          # uri: http://localhost:8045
          uri: lb://PRODUCT-SERVICE
          predicates:
            - Path=/product/**
        - id: cartservice
          # uri: http://localhost:8050
          uri: lb://CARTSERVICE
          predicates:
            - Path=/cart/**

 

 

LIST

+ Recent posts