Dubbo Spring Boot
Apache Dubbo
고성능, 가벼운 Java 기반 오픈 소스 RPC 프레임워크
- Interface 기반의 원격 호출(remote call)
- fault tolerance, load balancing
- Service 자동 등록 및 감지 (Service Registry)
RPC란?
- Remote Procedure Call
- 외부 서비스를 함수나 메소드를 통해 호출하는 형태
부가설명
분산서비스시 외부 서비스는 주로 HTTP를 통해 호출을 한다. 그리고 Application 레벨에서 header, body를 조작한다. 그러나 RPC는 마치 메소드를 호출하듯이 외부 서비스를 사용할 수 있는 간편함을 가지고 있다.
백문이 불여일타! 직접 코드를 작성해보자.
필자는 Spring boot와 편의상 maven 멀티모듈 구조를 통해서 코드를 작성하겠다. 구조는 아래와 같다.
Root
- provider
- consumer
또한 spring boot starter도 제공하기 때문에 더욱이 간단히 쓸 수 있다. 모든 코드가 다 작성된것이 아니기 때문에 Github의 코드와 같이 보면 좀 더 이해하기 쉬울 것 같다.
다음과 같은 분산시스템 흐름으로 만들어보자. 인터넷쇼핑에서 물건을 구입한다. 배송을 보내야하기 때문에 주소를 가진 회원정보를 가져와야한다. 물론 이외에도 많이 있겠지만 생략하겠다.
- 고객이 물건을 구입
- consumer에서 provider로 유저정보 조회
- 이후 작업
공통 Dependency - 필자는 부모 pom.xml에 설정을 하였다.
<properties> <dubbo.version>2.7.0</dubbo.version> </properties> <dependencies> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>${dubbo.version}</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>${dubbo.version}</version> </dependency> </dependencies>
Provider
누군가에게 특정 기능을 제공한다. 예를 들면 해당 Provider의 서비스가 회원과 관련된 서비스일 때 누군가 회원의 id로 조회를 하고싶으면 이 Provider를 이용하면 된다. 좀 더 쉽게말하면 Provider가 제공해주는 interface
를 단순히 호출하면 된다.
먼저, Dependency부터 설정하자.
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies>
DB에서 회원정보를 조회해야 하기때문에 인메모리 DB인 h2와 JPA를 설정하였다.
다음은 application.properties
에서 간단히 dubbo를 setting 하자.
# Spring boot application spring.application.name=dubbo-provider-demo # Dubbo Application ## dubbo.application.name=${spring.application.name} # Dubbo Protocol dubbo.protocol.name=dubbo dubbo.protocol.port=12345 ## Dubbo Registry dubbo.registry.address=N/A
dubbo.application.name
은 dubbo를 적용하려면 꼭 필요한 설정이다. 그러나 spring.application.name
이 있으면 이것을 default로 자동 설정하니 상황에 맞게하자. 필자는 그냥 spring.application.name
만 추가하였다.
그리고 protocol로 사용할 name, port를 설정한다. consumer는 이 정보를 통해 연결한다.
registry는 zookeeper같은 Service Registry를 의미한다. 본문에서는 다루지않으니 필요하다면 여기를 참고하자.
이제는 ProviderApplication
class로 가보자.
@DubboComponentScan @SpringBootApplication public class ProviderApplication implements CommandLineRunner { private final MemberRepository memberRepository; public ProviderApplication(MemberRepository memberRepository) { this.memberRepository = memberRepository; } public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); } @Override public void run(String... args) throws Exception { this.memberRepository.save(new Member("minsoo", 27,"강남구" ,LocalDateTime.now())); } }
먼저 눈에 띄는 것은 @DubboComponentScan
이다. Spring의 ComponentScan 처럼 Dubbo도 객체들을 Scan하는 과정이 필요하기 때문에 꼭 작성해주어야하며, annotation의 value를 이용하여 특정 package를 선택할 수도 있다.
그외에는 Test를 위해 DB에 data를 insert하는 로직이다.
다음은 consumer에서 provider를 사용하기 위한 수단으로서 interface를 제공해줘야한다.
public interface MemberService { Member getMember(Long id); }
import org.springframework.stereotype.Service; @Service @org.apache.dubbo.config.annotation.Service public class MemberServiceImpl implements MemberService { private final MemberRepository memberRepository; public MemberServiceImpl(MemberRepository memberRepository) { this.memberRepository = memberRepository; } @Override public Member getMember(Long id) { return this.memberRepository.findById(id) .orElseThrow(NotFoundMember::new); } private static class NotFoundMember extends RuntimeException { } }
첫번째 @Service
는 스프링 빈을 등록하는데 쓰이는 Annotation이다. 두번째 @Service
는 dubbo로 사용할 객체를 등록하는 Annotation이다. 두개의 이름이 똑같아서 불편함이 있긴한데, 두 Annotation을 상속받는 Custom Annotation을 하나 만들어서 사용하면 좀 더 간편할 것 같다.
@Entity public class Member implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Long id; private String name; private Integer age; private String addr; private LocalDateTime createDate; //getter, setter 등 생략 }
dubbo를 통해서 객체를 주고받을때 주의해야할 점은 Serializable
을 구현하고있어야만 데이터가 이동할 수 있다. 만약에 그러지 않으면 런타임에러를 내뱉는다. 그로 인해 Optional
같은 객체는 전달할 수는 없다.
Consumer
consumer은 provider에서 제공하는 서비스들을 사용한다.
이것도 Dependency 부터 살펴보자.
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.ms</groupId> <artifactId>provider</artifactId> <version>0.0.1-SNAPSHOT</version> <scope>compile</scope> </dependency> </dependencies>
spring mvc를 사용하기위해 spring-boot-starter-web
를 받았고, provider가 제공해주는 interface를 사용하기위해 provider 또한 디펜던시를 설정했다.
application.properties
에서 다음을 설정하자.
spring.application.name=dubbo-consumer-demo
consumer도 역시 name을 설정해줘야한다.
@RestController @RequestMapping("/orders") public class OrderController { private final Logger logger = LoggerFactory.getLogger(getClass()); @Reference(url = "dubbo://127.0.0.1:12345") private MemberService memberService; @PostMapping public String order(@RequestBody OrderRequest request) { final Member member = this.memberService.getMember(request.getMemberId()); logger.info("result ::: {}", member); //... return "success"; } }
여기서 눈여겨 볼것은 @Reference
이다. 이것을 스프링의 @Autowired
로 쉽게 생각하면된다. URL정보를 토대로 provider와 통신을 한다. 이 url은 provider에서 작성한 dubbo.protocol.name
, ip, dubbo.protocol.port
이다.
또한 그 아래 MemberService
는 provider에서 제공해주는 interface이다. consumer에는 이 MemberService
interface를 구현한 객체가 없으며, 구현한 객체는 provider에 있는 MemberServiceImpl
이다.
원격호출을 마치 local에 있는 메소드를 호출하는 것처럼 사용하니 참 간편하지 않은가?
이 처럼 직접적으로 호출을 할 수도 있고, Apache ZooKeeper나 Nacos의 Service Registry 기능을 통하여 간접적으로도 호출이 가능하다.
단, spring boot dubbo의 주의할 점은 아직 정식 Release되지 않은 Incubator 라는 점이다.
참고자료