AKKA는 자바환경에서 개발하는것이 원조이며
잠시 .net 환경을 벗어나 JAVA기반으로 형태소 분석기를 실시간 서비스하는
채팅 서비스를 만들어 보겠습니다. 예제작성은 Scala로 하였으며
Scala ↔ JAVA 100%변환및 호환되기 때문에 여기서는 구분을 하지 않겠습니다.
.net에서 설계한 액터와 크게 다르지 않고 , 우리는 이미 AKKA로 액터개발을통해
메시지처리가 되는 방법을 배웠으며
c# ↔ akka ↔ scala 적어도 이 3가지 언어는 AKKA의 컨셉을 통해 이동에따른
두려움이 줄어들것입니다. 원래 이 프로젝트의 목적은
C#에 쓸만한 형태소 분석기가없어서 JAVA의 형태소 분석 모듈을 실시간 마이크로서비스화
해서 C#의 액터와 실시간 상호운영을 하기위해 작성을 하였습니다.
구현되고보니 웹서비스/웹소켓/액터를 통한 메시징처리방법이
언어및 개발 프레임워크가 변한다고 해서 개발방법이 크게 달라지지 않습니다.
작동되는 풀소스는 아래에 있으니 참고하세요
git : https://github.com/psmon/psmonSearch ( Full Src )
구동모습
설계
다이어 그램이 엉성하지만, 위와같은 형태로 브라우져(Any플래폼) 에게 웹소켓을 통해
실시간 형태소 분석 서비스를 할수가 있습니다.
웹소켓 액터구현
package sock import actors.WordParserAcotor.AskParser import play.api.libs.json._ import akka.actor._ import play.api.libs.json.JsValue import models._ object MyWebSocketActor { def props(out: ActorRef, system:ActorSystem ) = Props(new MyWebSocketActor(out,system)) } class MyWebSocketActor(out: ActorRef, system:ActorSystem) extends Actor { def receive = { case msg: String => val json:JsValue = Json.parse(msg) val pid = (json \ "pid").asOpt[String] pid match { case Some("WordParserInfo") => val text:String = (json \ "text").as[String] val reqID:Int = (json \ "reqID").as[Int] system.actorSelection("/user/wordParserActor") ! AskParser(text,1,reqID) case None => out ! ("None") } case msg: JsValue => out ! msg.toString() case msg: SentenceMainListModel => out ! "ttt" } override def postStop() = { //someResource.close() } }
메시지를 받으면 wordParserActor(형태소 분석기) 에게 질의를 할것이며
형태소 분석기가 주는 값을 그대로 전달하는 기능만 있습니다.
형태소 분석기 액터 작성
package actors import play.api.libs.json._ import akka.actor._ import play.api.libs.json.JsValue import java.util.List import com.fasterxml.jackson.annotation.JsonValue import models._ import org.snu.ids.ha.ma.{Eojeol, MExpression, MorphemeAnalyzer, Sentence} import org.snu.ids.ha.index.Keyword import org.snu.ids.ha.index.KeywordExtractor import org.snu.ids.ha.index.KeywordList import scala.collection.JavaConverters._ import scala.collection.mutable.ListBuffer object WordParserAcotor { def props = Props[WordParserAcotor] case class Greeting(from: String) case object Goodbye case class AskParser(words:String,parserType:Int,seqID: Int) } class WordParserAcotor extends Actor with ActorLogging{ implicit val sentenceModelWrites = new Writes[SentenceModel] { def writes(data: SentenceModel) = Json.obj( "info" -> data.info ) } implicit val sentenceSubListModelWrites = new Writes[SentenceSubListModel] { def writes(data: SentenceSubListModel) = Json.obj( "name" -> data.name, "sentens" -> data.sentens ) } implicit val sentenceMainListModel = new Writes[SentenceMainListModel] { def writes(data: SentenceMainListModel) = Json.obj( "dataList" -> data.dataList ) } var ma:MorphemeAnalyzer = null; import WordParserAcotor._ def receive = { case Greeting(greeter) => log.info(s"I was greeted by $greeter.") case Goodbye => log.info("Someone said goodbye to me.") case AskParser(words,parserType,seqID) => if(parserType==1){ //val ma = new MorphemeAnalyzer var ret = ma.analyze(words) // refine spacing ret = ma.postProcess(ret) // leave the best analyzed result ret = ma.leaveJustBest(ret) // divide result to setences val stl:List[Sentence] = ma.divideToSentences(ret) var sentenceSubList:ListBuffer[SentenceSubListModel] = ListBuffer[SentenceSubListModel]() log.debug("==StlSize " + stl.size().toString()) for( stInfo <- stl.asScala ) { log.debug(stInfo.getSentence) val mainWord: String = stInfo.getSentence //var sentenceModelList: Seq[SentenceModel] = Seq[SentenceModel]() var sentenceModelList: ListBuffer[SentenceModel] = ListBuffer[SentenceModel]() val subInfo = stInfo.listIterator(); for(subItem <- subInfo.asScala ){ log.debug("==SubInfo " + subItem ) var sentenceModel = SentenceModel(subItem.toString()) //sentenceModelList = sentenceModelList :+sentenceModel sentenceModelList+=sentenceModel; log.debug(subItem.toString()) } var sentencSubListModel = SentenceSubListModel(mainWord, sentenceModelList) sentenceSubList+=sentencSubListModel }//End For val wordResult = SentenceMainListModel( "WordParserInfo", seqID ,sentenceSubList) val jsonData = Json.toJson(wordResult) sender ! jsonData }//end if } override def preStart()={ ma = new MorphemeAnalyzer } override def postStop()={ if(ma!=null){ ma.closeLogger(); } } }
꼬꼬마 형태소분석기를 호출하여 결과값을 그대로 웹소켓에 비동기로 반환합니다.
웹소켓 컨트롤러 연결
package controllers import javax.inject._ import play.api._ import play.api.mvc._ import play.api.libs.streams._ import akka.actor._ import akka.stream._ import sock._ import play.api.libs.json._ import scala.concurrent.Future @Singleton class SockController @Inject() (implicit system: ActorSystem, materializer: Materializer) extends play.api.mvc.Controller{ def socket = WebSocket.accept[String, String] { request => ActorFlow.actorRef(out => MyWebSocketActor.props(out,system)) } def testsock = Action { Ok(views.html.testsock ("Your WebSock is ready.")) } }
웹소켓 액터와, 주소 EndPoint를 연결하기위한 간단한 코드입니다. ( PlayFramework 참고)
HTML VIEW
@* * This template takes a single argument, a String containing a * message to display. *@ @(message: String) <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" /> <!--link rel="stylesheet" href="/stylesheets/style.css" /--> <link rel="stylesheet" href="http://code.jquery.com/mobile/git/jquery.mobile-git.css"> <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script> <script src="http://code.jquery.com/mobile/git/jquery.mobile-git.js"></script> <script src="@routes.Assets.versioned("javascripts/atmosphere.js")" type="text/javascript"></script> <script src="@routes.Assets.versioned("javascripts/websockModule.js")" type="text/javascript" ></script> </head> <body> <div id="header"><h3>Play 형태소 분석기 Test</h3></div> <div id="txtChatArea" style="overflow:auto;border:8px solid yellowgreen;padding:2%"> </div> </br> <input type="text" id="inChatStr"><button id="btnSend">Send</button> </body> </html>
브라우져 는 atmosphere.js 를 통해 서버와 실시간 채팅을 할것입니다.
단순한 실시간 채팅이 아니라. 서버는 내가 말하는것을 즉각적으로 형태소를 분석해줍니다.
Add Comment