웹 프레임웍을 위해 Spring Boot를 많이 사용하게 되며
기존 사용하는 Spring Boot에 AKKA를 내장시키는 방법을 알아보겠습니다.
빈을 통해 Akka를 SpringBoot에 내장 시키면서, Spring Boot의 Application Layout도 살펴보겠지만
Spring Boot을 사용하여 기본적인 웹 서비스 개발이 가능하다라고 가정하고 진행을 합니다.
메이븐설정
Spring Boot 1.5.9 와 Akka 2.4.8 버젼에서 테스트 되었습니다.
Application Layout
- 1 - Spring Boot의 진입점과 액터를 사용하는 샘플코드가 있습니다.
- 2 - 개발자가 설계한 각종 액터들을 집합을 시킬것입니다. 여기서 TestActor는 단순하게 받은 메시지 출력을 합니다.
- 3 - AkkaSystem의 초기화된 싱글톤 객체를 얻기위해서 SpringApplication에 빈을 등록 해야하며, Spring이 Akka의 라이프사이클을 관리해주게됩니다.
- 4 - Akka에서는 ActorOf라는 객체로 액터를 등록하며, Spring에서는 객체를 Bean으로 객체 관리합니다. 이 두가지를 동일한 인터페이스로 작동하게하는 유틸클래스입니다.
- 5 - 로그환경 통합및 Spring( application.properties) 의 외부설정과 Akka(application.confg) 의 외부설정을 할수 있는곳입니다.
Step1 - Akka 사용 준비하기 - (3,4번)
한번만 설정을 하면 이후 거의 변경될일이 없습니다.
Spring Boot에 기본으로 포함되지 않는 외부 오픈라이브러리를 Boot와 연동하는 주제이며
SpringBoot의 기본 구조를 익히는데 도움이 됩니다.
package com.psmon.cachedb.extension; import akka.actor.Actor; import akka.actor.IndirectActorProducer; import org.springframework.context.ApplicationContext; /** * An actor producer that lets Spring create the Actor instances. */ public class SpringActorProducer implements IndirectActorProducer { private final ApplicationContext applicationContext; private final String actorBeanName; public SpringActorProducer(ApplicationContext applicationContext, String actorBeanName) { this.applicationContext = applicationContext; this.actorBeanName = actorBeanName; } @Override public Actor produce() { return (Actor) applicationContext.getBean(actorBeanName); } @Override public Class<? extends Actor> actorClass() { return (Class<? extends Actor>) applicationContext.getType(actorBeanName); } }
package com.psmon.cachedb.extension; import akka.actor.Extension; import akka.actor.Props; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; /** * Extension to tell Akka how to create beans via Spring. */ @Component public class SpringExtension implements Extension { private ApplicationContext applicationContext; /** * Used to initialize the Spring application context for the extension. */ public void initialize(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } /** * Create a Props for the specified actorBeanName using the * SpringActorProducer class. */ public Props props(String actorBeanName) { return Props.create(SpringActorProducer.class, applicationContext, actorBeanName); } }
package com.psmon.cachedb.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.context.event.EventListener; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import com.psmon.cachedb.extension.SpringExtension; import akka.actor.ActorSystem; @Configuration @Lazy @ComponentScan(basePackages = { "com.psmon.cachedb.services", "com.psmon.cachedb.actors", "com.psmon.cachedb.extension" }) public class ApplicationConfiguration { @Autowired private ApplicationContext applicationContext; @Autowired private SpringExtension springExtension; @Bean public ActorSystem actorSystem() { ActorSystem system = ActorSystem .create("AkkaTestApp", akkaConfiguration()); springExtension.initialize(applicationContext); return system; } @Bean public Config akkaConfiguration() { return ConfigFactory.load(); } @EventListener(ApplicationReadyEvent.class) public void doSomethingAfterStartup() { } }
Step2 - 로그환경 준비하기 ( 5 )
동시성 메시지 처리는 로그파악이 중요함으로, 꼭 설정을한후 사용을 합니다.
그래야 초반부터 System.out.print(...) 와같은 코드를 사용하지 않고 로그기반으로
Trace를 하는 습관을 가지게 됩니다. 여기서는 logback 기본 콘솔모드 설정을 사용하였으며
logback은 다양한 방법의 로그방식을 추가할수 있는 유용한 로그 라이브러리 입니다.
#application.propertis logging.config=classpath:logback-spring.xml #application.conf akka { loggers = ["akka.event.Logging$DefaultLogger"] loglevel = "INFO" stdout-loglevel = "ERROR" } #logback-spring.xml <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%date{ISO8601} %-5level %X{akkaSource} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT" /> </root> </configuration>
Step3. 액터설계하기
받은 메시지를 단순하게 출력을 하는 TestActor입니다.
OOP와 무슨차이냐구요? 메시지 기반 설계는 로컬에서만 사용시 복잡도가 증가할수는 있으나
액터로 설계된것은 스레드세이프하고 클러스터로 확장될수 있는 가능성을 열어줍니다.
액터의 특징 참고자료 : Actor
package com.psmon.cachedb.actors; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import akka.actor.UntypedActor; import akka.event.Logging; import akka.event.LoggingAdapter; @Component @Scope("prototype") //액터객체는 수십만개를 생성해도 가볍습니다. 힙영역에 원하는 만큼 생성해야함으로 싱글톤으로 작동되는것을 방지합니다.(스프링특성) public class TestActor extends UntypedActor { private final LoggingAdapter log = Logging .getLogger(getContext().system(), "TestActor"); @Override public void onReceive(Object message) throws Exception { if(message instanceof String) { log.info("Incommessage {}", message); }else { log.info("Unhandle Message {}", message); } } }
Step.4 액터사용하기
package com.psmon.cachedb; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import com.psmon.cachedb.extension.SpringExtension; import akka.actor.ActorRef; import akka.actor.ActorSystem; import akka.event.Logging; import akka.event.LoggingAdapter; @SpringBootApplication public class CachedbApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(CachedbApplication.class, args); ActorSystem system = context.getBean(ActorSystem.class); final LoggingAdapter log = Logging.getLogger(system, "Application"); log.info("Starting up"); SpringExtension ext = context.getBean(SpringExtension.class); ActorRef testActor = system.actorOf(ext.props("testActor")); testActor.tell("ready spring boot",null); } }
주요코드
스프링 Context를 가지고 올수 있는곳 어느곳이나 AKKA사용을 위한 ActorSystem을 획득할수 있습니다.
ActorSystem system = context.getBean(ActorSystem.class);
객체명(TestActor = testActor(카멜) )을 통해 우리가 설계한 액터를 생성할수가 있습니다.
ActorRef testActor = system.actorOf(ext.props("testActor"));
액터의 참조명을 통해, 블락킹이 없는 메시지 처리가 가능합니다.
testActor.tell("ready spring boot",null);
작동로그확인
[INFO] [03/25/2018 20:24:00.055] [main] [Application] Starting up [INFO] [03/25/2018 20:24:00.068] [AkkaTestApp-akka.actor.default-dispatcher-2] [TestActor] Incommessage ready spring boot
실행후 위 로그를 확인하였다면.. Spring과 Akka의 연합작전이 성공한것입니다.
참조:
Akka+Spring : http://git.webnori.com/projects/WEBF/repos/spring_cachedb/commits/9a0b83d8a07cbe053dc20514546ac3fae05a8e23
Akka Document : https://akka.io/