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 를 통해 서버와 실시간 채팅을 할것입니다.

단순한 실시간 채팅이 아니라. 서버는 내가 말하는것을 즉각적으로 형태소를 분석해줍니다.


atmosphere는 웹소켓이 지원되지 않는 브라우져에서, 롱폴링으로 전환되는

node.js의 socket.io / asp.net signalR 와 지원 스펙이 유사한 자바진영 웹소켓 모듈입니다.

참고: https://github.com/Atmosphere/atmosphere







  • No labels
Write a comment…