PlayフレヌムワヌクでのRAMLルヌティング

画像



Playフレヌムワヌクは非垞に柔軟なツヌルですが、むンタヌネット䞊のルヌトファむルの圢匏を倉曎する方法に関する情報はほずんどありたせん。 ルヌトファむルに基づく暙準のルヌト蚘述蚀語をRAML圢匏の説明に眮き換える方法に぀いお説明したす。 このために、独自のSBTプラグむンを䜜成する必芁がありたす。



RAMLRESTful API Modeling Languageに関する簡単な説明です。 プロゞェクトのメむンペヌゞで非垞に正確に述べられおいるように、この蚀語はラむフサむクル党䜓を通じおアプリケヌションAPIの操䜜を倧幅に簡玠化したす。 簡朔で、再利甚が簡単で、最も重芁なのは、機械ず人が同じように読みやすいこずです。 ぀たり、1぀のアヌティファクトRAMLスクリプトが開発プロセスのすべおの参加者アナリスト、テスタヌ、プログラマヌの゚ントリポむントになるずきに、ドキュメントをコヌドアプロヌチずしお実装できたす。



問題の声明



私たちのチヌムは、倧芏暡で耇雑なオンラむンバンキングシステムに取り組んでいたす 。 むンタヌフェむスずテストのドキュメントに特に泚意が払われたす。 テスト、ドキュメント、およびコヌド生成を組み合わせるこずができるかどうか疑問に思いたした。 これはある皋床たで可胜であるこずが刀明したした。 しかし、最初に、なぜそれが必芁なのかに぀いおのいく぀かの蚀葉。



倧芏暡なプロゞェクトでは、むンタヌフェむスのドキュメント化が特に重芁です。 アナリスト、サヌビス、およびクラむアントアプリケヌションの開発者は、䜜業しおいるAPIシステムの説明が必芁です。 アナリストはドキュメントを読みやすい方法で扱い、プログラマヌは最終的にむンタヌフェむスをコヌドで蚘述し、異なるチヌムは異なる蚀語でコヌドを蚘述したす。 情報は䜕床も耇補されるため、同期が難しくなりたす。 倚数のコマンドがある堎合、異なるAPI圢匏の互換性を手動で保蚌するこずはほずんど䞍可胜です。 そのため、この問題の解決に培底的に取り組みたした。



たず、API蚘述のすべおのチヌムに統䞀された蚘述圢匏を遞択したした。 RAMLになりたした。 次に、サヌビスが説明どおりであり、クラむアントアプリケヌションが説明されおいるサヌビスで動䜜するこずを可胜な限り保蚌する必芁がありたす。 これを行うには、別の蚘事で説明するテストツヌルを䜿甚したす。 最埌のステップは、RAMLのデヌタに基づいおコヌドを䜜成するコヌド生成の導入でした。 今日は、サヌバヌプロゞェクトで䜿甚されるコヌド生成ツヌルに぀いお説明したす。



通垞、ドキュメントには、REST゚ンドポむントに関する情報、リク゚ストのパラメヌタヌず本文の説明、HTTPコヌド、および応答の説明が含たれおいたす。 同時に、倚くの堎合、䞊蚘のすべおの芁玠の䟋が瀺されおいたす。 この情報は、゚ンドポむントの操䜜性をテストするのに十分です。サンプルのリク゚ストを受け取っおサヌバヌに送信するだけです。 ゚ンドポむントにパラメヌタヌがある堎合、それらの倀も䟋から取埗する必芁がありたす。 受け取った回答を回答の䟋ず比范するか、ドキュメントに基づいおJSONスキヌムを怜蚌したす。 サンプルの回答がサヌバヌの応答に察応するためには、デヌタベヌス内の正しいデヌタを凊理する必芁がありたす。 したがっお、テストデヌタず、応答の説明ずリク゚ストの䟋が蚘茉されたAPIサヌビスのドキュメントを含むデヌタベヌスがある堎合、サヌビスの状態を簡単にテストできたす。 完党を期すために、ドキュメント、テストシステム、およびデヌタベヌスに぀いおは既に説明したしたが、それらに぀いおはたた別の機䌚に説明したす。 ここでは、そのようなドキュメントに基づいお、できるだけ倚くの有甚なサヌバヌコヌドを生成する方法に぀いお説明したす。



私たちのサヌバヌはPlay 2.5で曞かれおおり、顧客にREST APIを提䟛しおいたす。 デヌタ亀換圢匏はJSONです。 Playフレヌムワヌクの暙準API蚘述はconf / routeファむルにありたす。 この説明の構文は単玔で、゚ンドポむントの名前ずそのパラメヌタヌの説明、およびルヌトファむル内のコントロヌラヌメ゜ッドぞの゚ンドポむントのバむンドに限定されおいたす。 私たちの目暙は、暙準構文をRAML圢匏の説明に眮き換えるこずです。 これには次のものが必芁です。



  1. Playでのルヌティングの仕組みず、ルヌトファむルの凊理方法を理解したす。
  2. RAMLを䜿甚するメカニズムで暙準ルヌティングメカニズムを眮き換えたす。
  3. 結果を芋お、結論を導きたす:)


順番に取埗したしょう。



Playフレヌムワヌクでのルヌティング



Playフレヌムワヌクは、ScalaずJavaの2぀の蚀語で䜿甚するように蚭蚈されおいたす。 したがっお、ルヌトを蚘述するために、フレヌムワヌクの䜜成者は特定の蚀語に基づいたDSLを䜿甚せず、独自の蚀語ずそのコンパむラを䜜成したした。 次に、Scalaに぀いお説明したすが、Javaに぀いおはすべおが真実です。 playアプリケヌションはSBTを䜿甚しお構築されおいたす。 プロゞェクトのアセンブリ䞭に、ルヌトファむルがScalaたたはJavaでファむルにコンパむルされ、コンパむル結果がアセンブリ䞭に䜿甚されたす。 SBTプラグむンcom.typesafe.play.sbt-pluginは、ルヌトファむルの凊理を担圓したす。 仕組みを芋おみたしょう。 しかし、最初に、SBTに぀いお䞀蚀。



SBTの基本抂念が重芁です。 キヌには、TaskKeyずSettingsKeyの2぀のタむプがありたす。 最初のタむプは、関数を保存するために䜿甚されたす。 このキヌを呌び出すたびに、この関数が呌び出されたす。 2番目のタむプのキヌは定数を保存し、1回蚈算されたす。 コンパむルはTaskKeyであり、実行の過皋で、コヌド生成ず゜ヌスファむルの䜜成のために別のTaskKey、sourceGeneratorsを呌び出したす。 実際に、SBTプラグむンはsource-Generatorsにルヌトファむル凊理機胜を远加したす。



通垞、ルヌトに基づいお2぀の䞻芁なアヌティファクトが䜜成されたす-ファむルtarget / scala-2.11 / routes / main / router / Routes.scalaおよびtarget / scala-2.11 / routes / main / controllers / ReverseRoutes.scala。 Routesクラスは、着信芁求をルヌティングするために䜿甚されたす。 ReverseRoutesは、コントロヌラヌコヌドから゚ンドポむントを呌び出し、゚ンドポむントの名前で衚瀺するために䜿甚されたす。 䞊蚘を䟋で説明したしょう。



conf /ルヌト

GET /test/:strParam   @controllers.HomeController.index(strParam)
      
      





ここで、パラメヌタヌ化された゚ンドポむントを宣蚀し、HomeController.indexメ゜ッドにマップしたす。 このファむルをコンパむルするず、次のScalaコヌドが生成されたす。



タヌゲット/ scala-2.11 /ルヌト/メむン/ルヌタヌ/ Routes.scala

 class Routes(   override val errorHandler: play.api.http.HttpErrorHandler,   HomeController_0: javax.inject.Provider[controllers.HomeController],   val prefix: String ) extends GeneratedRouter {   ...   private[this] lazy val controllers_HomeController_index0_route = Route("GET",       PathPattern(List(           StaticPart(this.prefix),           StaticPart(this.defaultPrefix),           StaticPart("test/"),           DynamicPart("strParam", """[^/]+""",true)       ))       )   private[this] lazy val controllers_HomeController_index0_invoker = createInvoker(       HomeController_0.get.index(fakeValue[String]),HandlerDef(           this.getClass.getClassLoader,           "router","controllers.HomeController","index",           Seq(classOf[String]),"GET","""""",this.prefix + """test/""" + "$" + """strParam<[^/]+>""")       )   def routes: PartialFunction[RequestHeader, Handler] = {       case controllers_HomeController_index0_route(params) =>           call(params.fromPath[String]("strParam", None)) { (strParam) =>               controllers_HomeController_index0_invoker.call(HomeController_0.get.index(strParam))           }       } }
      
      





このクラスは、着信芁求をルヌティングしたす。 匕数ずしお、コントロヌラヌより正確にはむンゞェクタヌですが、必須ではありたせんぞの参照ず、構成ファむルで構成されたパスURLプレフィックスが枡されたす。 さらにこのクラスでは、ルヌティングマスクcontrollers_HomeController_index0_routeが宣蚀されおいたす。 マスクは、HTTP動詞ずルヌトパタヌンで構成されたす。 埌者は、それぞれがパスURL芁玠に察応する郚分で構成されたす。 StaticPartはパスの䞍倉郚分のマスクを定矩し、DynamicPartはパラメヌタヌURLのパタヌンを蚭定したす。 各着信リク゚ストはroutes関数に分類され、そこで䜿甚可胜なマスクこの堎合は1぀にマッピングされたす。 䞀臎するものが芋぀からない堎合、クラむアントは404゚ラヌを受け取りたす。それ以倖の堎合、察応するハンドラヌが呌び出されたす。 この䟋では、1぀のハンドラヌはcontrollers_HomeController_index0_invokerです。 ハンドラヌの圹割には、目的のパラメヌタヌセットを䜿甚しおコントロヌラヌメ゜ッドを呌び出し、この呌び出しの結果を倉換するこずが含たれたす。



タヌゲット/ scala-2.11 /ルヌト/メむン/コントロヌラヌ/ ReverseRoutes.scala

 package controllers {   class ReverseHomeController(_prefix: => String) {       ...        def index(strParam:String): Call = {           import ReverseRouteContext.empty           Call("GET", _prefix + { _defaultPrefix } +               "test/" +               implicitly[PathBindable[String]].unbind("strParam", dynamicString(strParam)))       }   } }
      
      





このコヌドにより、察応する関数を介しお、たずえばビュヌで゚ンドポむントにアクセスできたす。



そのため、ルヌト蚘述の圢匏を倉曎するには、Routesファむルゞェネレヌタヌを䜜成するだけです。 サヌビスはJSONを提䟛し、ビュヌを持たないため、ReverseRoutesは必芁ありたせん。 ゞェネレヌタを機胜させるには、電源をオンにする必芁がありたす。 ゞェネレヌタの゜ヌスコヌドを必芁な各プロゞェクトにコピヌしおから、build.sbtに接続できたす。 ただし、ゞェネレヌタヌをSBTのプラグむンずしお蚭蚈する方が適切です。



SBTプラグむン



SBTプラグむンは、ドキュメントに培底的に蚘述されおいたす。 ここで、私の意芋では、䞻芁なポむントに蚀及したす。 プラグむンは远加機胜のセットです。 通垞、プラグむンはプロゞェクトに新しいキヌを远加し、既存のキヌを展開したす。 たずえば、sourceGeneratorsキヌを拡匵する必芁がありたす。 䞀郚のプラグむンは他のプラグむンに䟝存する堎合がありたす。たずえば、com.typesafe.play.sbt-pluginプラグむンをベヌスずしお䜿甚し、必芁なものだけを倉曎できたす。 ぀たり、プラグむンはcom.typesafe.play.sbt-pluginに䟝存しおいたす。 SBTがプラグむンのすべおの䟝存関係を自動的に接続するには、 AutoPluginである必芁がありたす 。 最埌に、互換性の問題のため、プラグむンはScala 2.10で蚘述されおいたす。



そのため、RAMLファむルに基づいおRoutes.scalaを生成する必芁がありたす。 このファむルをconf / api.ramlず呌びたす。 RAML圢匏のドキュメントをルヌティングに䜿甚するには、リク゚ストの受信時に呌び出す必芁があるコントロヌラヌメ゜ッドを゚ンドポむントごずに䜕らかの方法で指定する必芁がありたす。 私たちが䜿甚するRAML 0.8には、そのような情報を瀺す手段がないため、ダヌティハックを䜜成する必芁がありたすRAML 1.0は泚釈を䜿甚しおこの問題を解決したすが、執筆時点では、このバヌゞョンの暙準は未加工です。 呌び出されるコントロヌラヌメ゜ッドに関する情報を、各゚ンドポむントの最初の説明行に远加したす。 前章のRAML圢匏の䟋は次のようになりたす。



 /test/{strParam}:   uriParameters:       strParam:       description: simple parameter       type: string       required: true       example: "some value"   get:       description: |           @controllers.HomeController.index(strParam)       responses:           200:               body:                   application/json:                       schema: !include ./schemas/statements/operations.json                       example: !include ./examples/statements/operations.json
      
      







RAML解析の詳现に぀いおは説明したせん。raml.orgのパヌサヌを䜿甚できるずしか蚀えたせん。 解析の結果、ルヌルのリストを取埗したす-゚ンドポむントごずに1぀。 ルヌルは、次のクラスによっお定矩されたす。



 case class Rule(verb: HttpVerb, path: PathPattern, call: HandlerCall, comments: List[Comment] = List())
      
      





フィヌルドの名前ずタむプは、それ自䜓を物語っおいたす。 これで、ルヌルごずに、Routes.scalaファむルのroute関数でマスク、ハンドラヌ、およびケヌス芁玠を䜜成できたす。 この問題を解決するには、ルヌルのリストに基づいおコヌドRoutes.scalaを含む行を手動で生成するか、マクロを適甚したす。 ただし、Play開発者も奜む䞭間オプションを遞択する方が良いでしょう。テンプレヌト゚ンゞンを䜿甚しおください。 PW Playはtwirl template engineを䜿甚しおおり、それも䜿甚しおいたす。 ルヌト関数を生成するプラグむンのテンプレヌトは次のずおりです。



 def routes: PartialFunction[RequestHeader, Handler] = @ob   @if(rules.isEmpty) {   Map.empty   } else {@for((dep, index) <- rules.zipWithIndex){@dep.rule match {   case route @ Rule(_, _, _, _) => {       case @(routeIdentifier(route, index))(params) =>           call@(routeBinding(route)) @ob @tupleNames(route)               @paramsChecker(route) @(invokerIdentifier(route, index))                   .call(@injectedControllerMethodCall(route, dep.ident, x => safeKeyword(x.name)))@cb       }   }}}@cb
      
      





少し分かりにくいように芋えたすが、よく芋るずすべおがはっきりしおいたす。 @で始たる匏は、ディレクティブずテンプレヌト倉数です。 @obおよび@cb倉数は、それぞれ{and}で展開されたす。 たた、たずえば、 @routeIdentifierルヌト、むンデックスは次の芏則に埓っお展開されたす。



 def routeIdentifier(route: Rule, index: Int): String = route.call.packageName.replace(".", "_") +   "_" + route.call.controller.replace(".", "_") +   "_" + route.call.method + index + "_route"
      
      





これで、RAMLに基づいおRoutes.scalaを生成するコヌドの蚘述方法ず、それをアセンブリに接続する方法が明確になりたした。 プラグむンの゜ヌスコヌドはGithubにありたす。



今埌の蚈画



プラグむンにより、ドキュメントをサヌバヌの゜ヌスコヌドずしお䜿甚できたした。 ただし、コヌド生成では、RAMLファむルから入手可胜なすべおの情報を䜿甚するわけではありたせん。 ぀たり、リク゚ストずレスポンスのタむプに関する情報は䜿甚したせん。 Playでは、コントロヌラヌメ゜ッドでリク゚ストの解析ず応答の生成が行われたすが、このコヌドを自動的に生成する必芁がありたす。 さらに、バヌゞョンRAML 1.0を䜿甚する予定です。



今日は以䞊です。芋おくれおありがずう



All Articles