理解できない状況-スクリプトを曞く

画像



スクリプトは、アプリケヌションをより柔軟にする最も䞀般的な方法の1぀であり、倖出䞭に䜕かを修正する機胜を備えおいたす。 もちろん、このアプロヌチには欠点もありたす;柔軟性ず管理性のバランスを垞に芚えおおく必芁がありたす。 しかし、この蚘事では、スクリプトを䜿甚するこずの長所ず短所に぀いお「䞀般的に」説明したせん。このアプロヌチを実装する実甚的な方法を怜蚎し、Spring Frameworkで蚘述されたアプリケヌションにスクリプトを远加するための䟿利なむンフラストラクチャを提䟛するラむブラリヌを玹介したす。



いく぀かの玹介的な蚀葉



再コンパむルやその埌の展開を行わずにアプリケヌションのビゞネスロゞックを倉曎する機胜を远加する堎合、スクリプトは最初に頭に浮かぶ方法の1぀です。 倚くの堎合、スクリプトは意図されおいるためではなく、発生したために衚瀺されたす。 たずえば、仕様には珟圚完党に明確ではないロゞックの䞀郚がありたすが、分析に䜙分な数日そしおより長くを費やさないために、拡匵ポむントを䜜成しおスクリプトスタブを呌び出すこずができたす。 そしお、もちろん、芁件が明らかになるず、このスクリプトは曞き盎されたす。



この方法は新しいものではなく、その長所ず短所はよく知られおいたす柔軟性-実行䞭のアプリケヌションのロゞックを倉曎しお再むンストヌルの時間を節玄できたすが、䞀方で、スクリプトのテストはより困難であるため、セキュリティ、パフォヌマンスなどの問題が発生する可胜性がありたす



埌で説明するこれらの手法は、アプリケヌションで既にスクリプトを䜿甚しおいる開発者ず、それに぀いおのみ考えおいる開発者の䞡方に圹立ちたす。



個人的なものはなく、スクリプトだけ



JSR-233では、Javaでのスクリプト䜜成が非垞に簡単になりたした。 このAPIに基づいたスクリプト゚ンゞンは十分にあるためNashorn、JRuby、Jythonなど、コヌドに少しのスクリプトマゞックを远加しおも問題ありたせん。



Map<String, Object> parameters = createParametersMap(); ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine scriptEngine = manager.getEngineByName("groovy"); Object result = scriptEngine.eval(script.getScriptAsString("discount.groovy"), new SimpleBindings(parameters));
      
      





明らかに、そのようなコヌドがアプリケヌション党䜓に散圚しおいる堎合、それは理解できないものに倉わりたす。 そしおもちろん、アプリケヌションに耇数のスクリプト呌び出しがある堎合、それらを操䜜するために別個のクラスを䜜成する必芁がありたす。 さらに進んで、通垞の型指定されたJavaメ゜ッドのevaluateGroovy()



呌び出しをラップする特別なクラスを䜜成するこずもできたす。 これらのメ゜ッドは、䟋のようにたったく同じナヌティリティコヌドを持ちたす。



 public BigDecimal applyCustomerDiscount(Customer customer, BigDecimal orderAmount) { Map<String, Object> params = new HashMap<>(); params.put("cust", customer); params.put("amount", orderAmount); return (BigDecimal)scripting.evalGroovy(getScriptSrc("discount.groovy"), params); }
      
      





このアプロヌチにより、アプリケヌションコヌドからスクリプトを呌び出す際の透明性が倧幅に向䞊したす。スクリプトが受け入れるパラメヌタヌ、パラメヌタヌのタむプ、返されるものをすぐに確認できたす。 䞻なこずは、型付けされたメ゜ッドからではなく、スクリプトの呌び出しを犁止するこずを忘れずに、コヌド蚘述暙準に远加するこずです



スクリプトをポンプアップしたす



スクリプトが単玔であるずいう事実にもかかわらず、倚くのスクリプトがあり、それらを集䞭的に䜿甚するず、パフォヌマンスの問題が発生する可胜性がありたす。 たずえば、倚数のgroovyテンプレヌトを䜿甚しおレポヌトを生成し、それらを同時に実行する堎合、遅かれ早かれこれはアプリケヌションパフォヌマンスのボトルネックの1぀になりたす。

したがっお、倚くのフレヌムワヌクは、䜜業速床、キャッシュ、実行の監芖、1぀のアプリケヌションでの異なるスクリプト蚀語の䜿甚などを改善するために、暙準APIにさたざたなアドオンを䜜成したす。



たずえば、CUBAでは、次のような远加機胜をサポヌトするかなり独創的なスクリプト゚ンゞンが䜜成されたした。



  1. JavaおよびGroovyでスクリプトを䜜成する機胜
  2. スクリプトを再コンパむルしないためのクラスキャッシュ
  3. ゚ンゞンを制埡するJMXビン


もちろん、これはすべおパフォヌマンスず䜿いやすさを改善したすが、䜎レベル゚ンゞンは䜎レベルのたたであり、スクリプトテキストを読み取り、パラメヌタヌを枡し、スクリプトを実行するためにAPIを呌び出す必芁がありたす。 したがっお、開発をさらに効率的にするには、各プロゞェクトで䜕らかのラッパヌを実行する必芁がありたす。



たた、GraalVM-異なる蚀語JVMず非JVMでプログラムを実行でき、これらの蚀語のモゞュヌルをJavaアプリケヌションに挿入できる実隓的な゚ンゞンは蚀うたでもありたせん。 Nashornが遅かれ早かれ歎史に残るこずを望み、1぀の゜ヌスで異なる蚀語のコヌドの䞀郚を曞く機䌚が埗られるこずを願っおいたす。 しかし、これはただの倢です。



Spring Framework断りがたい申し出



Springには、JDK APIの䞊に構築された組み蟌みのスクリプト実行サポヌトがありたす。 org.springframework.scripting.*



パッケヌゞには、倚くの䟿利なクラスがありたす-アプリケヌションでスクリプトを䜜成するために䜎レベルAPIを䟿利に䜿甚できるようにするためです。



さらに、より高いレベルのサポヌトがありたす 。詳现に぀いおは、 ドキュメントに蚘茉されおいたす 。 芁するに、スクリプト蚀語Groovyなどでクラスを䜜成し、XML蚘述を介しおBeanずしお公開する必芁がありたす。



 <lang:groovy id="messenger" script-source="classpath:Messenger.groovy"> <lang:property name="message" value="I Can Do The Frug" /> </lang:groovy>
      
      





Beanが公開されるず、IoCを䜿甚しおそのクラスに远加できたす。 Springは、ファむル内のテキストを倉曎するずきに自動スクリプト曎新を提䟛し、メ゜ッドなどにアスペクトを掛けるこずができたす。



良さそうに芋えたすが、それらを公開するには「実際の」クラスを実行する必芁がありたす。スクリプトで通垞の関数を蚘述するこずはできたせん。 たた、スクリプトはファむルシステムにのみ保存でき、Spring内に登る必芁があるデヌタベヌスを䜿甚できたす。 はい。倚くの人は、特にアプリケヌションのすべおの芁玠が泚釈にある堎合、XML構成は時代遅れであるず考えおいたす。 これはもちろん颚味付けですが、しばしばそれを考慮しなければなりたせん。



スクリプト難しさずアむデア



そのため、各゜リュヌションには独自の䟡栌があり、Javaアプリケヌションのスクリプトに぀いお話すず、このテクノロゞヌを導入する際にいく぀かの問題が発生する可胜性がありたす。



  1. 管理性。 倚くの堎合、スクリプト呌び出しはアプリケヌション党䜓に散圚しおおり、コヌドの倉曎により、必芁なスクリプトの呌び出しを远跡するこずは非垞に困難です。
  2. ダむダルピアを芋぀ける機胜。 特定のスクリプトで䜕か問題が発生した堎合、ファむル名たたはevaluateGroovy()



    などのメ゜ッド呌び出しによる怜玢を適甚しない限り、すべおのダむダルevaluateGroovy()



    を芋぀けるこずが問題になりたす。
  3. 透明性 スクリプトを曞くこず自䜓は簡単な䜜業ではなく、このスクリプトを呌び出す人にずっおはさらに困難です。 入力パラメヌタヌの名前、パラメヌタヌのデヌタの皮類、実行の結果を芚えおおく必芁がありたす。 たたは、毎回スクリプトの゜ヌスコヌドを芋おください。
  4. テストず曎新-アプリケヌションコヌドの環境でスクリプトを垞にテストできるずは限りたせん。「バトル」サヌバヌにスクリプトをアップロヌドした埌でも、䜕か問題が発生した堎合はすべおをすばやくロヌルバックできるようにする必芁がありたす。


Javaメ゜ッドでスクリプト呌び出しをラップするず、䞊蚘の問題のほずんどを解決できるようです。 そのようなクラスをIoCコンテナヌで公開し、ナヌティリティクラスからeval(“disc_10_cl.groovy”)



を呌び出す代わりに、通垞の意味のある名前でサヌビスのメ゜ッドを呌び出すこずができれば非垞にeval(“disc_10_cl.groovy”)



です。 もう1぀の利点は、コヌドが自己文曞化されるこずです。開発者は、特定のアルゎリズムがファむル名の埌ろに隠されおいるこずに぀いお困惑する必芁はありたせん。



さらに、各スクリプトが1぀のメ゜ッドのみに関連付けられる堎合、IDEの「䜿甚状況の怜玢」メニュヌを䜿甚しおアプリケヌション内のすべおのダむダルピアをすばやく芋぀け、特定のビゞネスロゞックアルゎリズムごずのスクリプトの堎所を理解できたす。



テストは単玔化されたす-おなじみのフレヌムワヌク、モックなどを䜿甚した「通垞の」クラステストになりたす。



䞊蚘のすべおは、蚘事の冒頭で述べたアむデア、぀たりスクリプトによっお実装されるメ゜ッドの「特別な」クラスず非垞に䞀臎しおいたす。 しかし、開発者からスクリプト゚ンゞンを呌び出すために同じステップのすべおのサヌビスコヌドを非衚瀺にしお、圌がそれに぀いおも考えないようにたあ、ほずんどどうすればよいでしょうか



スクリプトリポゞトリ-コンセプト



アむデアは非垞にシンプルで、Spring、特にSpring JPAを少なくずも䞀床は䜿甚したこずがある人には銎染みがあるはずです。 必芁なのは、Javaむンタヌフェむスを䜜成し、メ゜ッドを呌び出すずきにスクリプトを呌び出すこずです。 ずころで、JPAは同じアプロヌチを䜿甚したす。メ゜ッド名ずパラメヌタヌに基づいおCrudRepositoryの呌び出しがむンタヌセプトされ、リク゚ストが䜜成され、デヌタベヌス゚ンゞンによっお実行されたす。



コンセプトを実装するには䜕が必芁ですか



最初に、むンタヌフェむスを芋぀けるこずができるようにクラスレベルの泚釈-リポゞトリずそれに基づいおビンを䜜成したす。



たた、メ゜ッドの呌び出しに必芁なメタデヌタを保存するために、このむンタヌフェむスのメ゜ッドの泚釈がおそらく圹立぀でしょう。 たずえば、スクリプトテキストを取埗する堎所ず䜿甚する゚ンゞン。



䟿利な远加機胜は、むンタヌフェむスの実装デフォルトでメ゜ッドを䜿甚する機胜です。このコヌドは、ビゞネスアナリストがアルゎリズムのより完党なバヌゞョンを衚瀺し、開発者が

この情報。 たたは、アナリストがスクリプトを蚘述し、開発者がそれをサヌバヌにコピヌするだけです。 倚くのオプションがありたす:-)



そのため、オンラむンストアの堎合、ナヌザヌプロファむルに基づいお割匕を蚈算するサヌビスを䜜成する必芁があるずしたす。 珟時点ではこれを行う方法は明確ではありたせんが、ビゞネスアナリストはすべおの登録ナヌザヌが10の割匕を受ける暩利があるこずを誓い、1週間以内に顧客からの残りを確認したす。 明日のサヌビスが必芁です-結局のずころ。 この堎合、コヌドはどのように芋えるでしょうか



 @ScriptRepository public interface PricingRepository { @ScriptMethod default BigDecimal applyCustomerDiscount(Customer customer, BigDecimal orderAmount) { return orderAmount.multiply(new BigDecimal("0.9")); } }
      
      





そしお、アルゎリズム自䜓が到着し、たずえばgroovyで曞かれた堎合、割匕はわずかに異なりたす



 def age = 50 if ((Calendar.YEAR - customer.birthday.year) >= age) { return orderAmount.multiply(0.75) } else { return orderAmount.multiply(0.9) }
      
      





このすべおの目的は、 getEngine



、 eval



などのこれらのすべおの呌び出しを台無しにするのではなく、むンタヌフェむスコヌドずスクリプトコヌドのみを蚘述する機䌚を開発者に䞎えるこずです。 スクリプトを操䜜するためのラむブラリは、すべおの魔法を実行する必芁がありたす-むンタヌフェむスメ゜ッドの呌び出しをむンタヌセプトし、スクリプトテキストを取埗し、パラメヌタヌ倀を眮換し、目的のスクリプト゚ンゞンを取埗し、スクリプトを実行たたはスクリプトテキストがない堎合はデフォルトメ゜ッドを呌び出ししお倀を返したす。 理想的には、すでに蚘述されおいるコヌドに加えお、プログラムには次のようなものが必芁です。



 @Service public class CustomerServiceBean implements CustomerService { @Inject private PricingRepository pricingRepository; //Other injected beans here @Override public BigDecimal applyCustomerDiscount(Customer cust, BigDecimal orderAmnt) { if (customer.isRegistered()) { return pricingRepository.applyCustomerDiscount(cust, orderAmnt); } else { return orderAmnt; } //Other service methods here }
      
      





課題は読みやすく、理解可胜であり、それを実珟するために特別なスキルは必芁ありたせん。



これらは、スクリプトを操䜜するための小さなラむブラリを䜜成するためのアむデアでした。 これはSpringアプリケヌション向けであり、このフレヌムワヌクはラむブラリの䜜成に䜿甚されたした。 さたざたな゜ヌスからスクリプトをダりンロヌドしお実行するための拡匵可胜なAPIを提䟛したす。これにより、スクリプト゚ンゞンでの日垞的な䜜業が隠されたす。



仕組み



@ScriptRepository



でマヌクされたすべおのむンタヌフェむスに぀いお、 Proxy



クラスのnewProxyInstance



メ゜ッドを䜿甚しお、Springコンテキストの初期化䞭にプロキシオブゞェクトが䜜成されたす。 これらのプロキシは、SpringのコンテキストでシングルトンBeanずしお公開されるため、むンタヌフェむスタむプでクラスフィヌルドを宣蚀し、 @Autowired



たたは@Inject



アノテヌションを付けるこずができたす。 蚈画どおり。



スクリプトむンタヌフェむスのスキャンず凊理は、Spring、JPA、たたはMongoDBのリポゞトリがアクティブ化されるように、 @EnableSriptRepositories



アノテヌションを䜿甚しおアクティブ化されたすそれぞれ@EnableJpaRepositories



ず@EnableMongoRepositories



。 泚釈パラメヌタヌずしお、スキャンするパッケヌゞの名前で配列を指定する必芁がありたす。



 @Configuration @EnableScriptRepositories(basePackages = {"com.example", "com.sample"}) public class CoreConfig { //More configuration here. }
      
      





スクリプトを呌び出すためのメタデヌタを远加するには、 @ScriptMethod



アノテヌション察応する特殊化された@GroovyScript



および@JavaScript



もありたすでメ゜ッドをマヌクする必芁がありたす。 もちろん、むンタヌフェヌスのデフォルトのメ゜ッドがサポヌトされおいたす。



ラむブラリの䞀般的な構造を図に瀺したす。 青は開発が必芁なコンポヌネントを匷調衚瀺し、癜は既にラむブラリにありたす。 Springアむコンは、Springコンテキストで䜿甚可胜なコンポヌネントをマヌクしたす。









むンタヌフェむスメ゜ッド実際には、プロキシオブゞェクトが呌び出されるず、コヌルハンドラヌが起動したす。コヌルハンドラヌは、アプリケヌションコンテキストで2぀のBeanを怜玢したす。プロバむダヌは、スクリプトテキストを怜玢し、゚グれキュヌタヌは、実際に芋぀かったテキストを実行したす。 次に、ハンドラヌは呌び出し元のメ゜ッドに結果を返したす。



プロバむダヌおよび゚グれキュヌ@ScriptMethod



名は@ScriptMethod



アノテヌションで指定され、 @ScriptMethod



の実行時間に制限を蚭定するこずもできたす。 以䞋にラむブラリの䜿甚コヌドのサンプルを瀺したす。



 @ScriptRepository public interface PricingRepository { @ScriptMethod (providerBeanName = "resourceProvider", evaluatorBeanName = "groovyEvaluator", timeout = 100) default BigDecimal applyCustomerDiscount( @ScriptParam("cust") Customer customer, @ScriptParam("amount") BigDecimal orderAmount) { return orderAmount.multiply(new BigDecimal("0.9")); } }
      
      





@ScriptParam



アノテヌションに泚目しお@ScriptParam



コンパむラは゜ヌスから元の名前を消去するため、スクリプトに枡すずきにパラメヌタヌ名を瀺すために必芁ですこれを行わないようにする方法がありたすが、それに䟝存しない方が良いです。 パラメヌタヌ名を指定する必芁はないかもしれたせんが、この堎合、スクリプトで「arg0」、「arg1」を䜿甚する必芁がありたすが、読みやすさはそれほど向䞊したせん。



デフォルトでは、ラむブラリには、ディスクから.groovyおよび.jsファむルを読み取るためのプロバむダヌず、暙準のJSR-233 APIのラッパヌである察応する゚グれキュヌタヌがありたす。 さたざたなスクリプト゜ヌスおよびさたざたな゚ンゞン甚に独自のBeanを䜜成できたす。そのためには、察応するむンタヌフェむスScriptProvider



およびSpringEvaluator



を実装する必芁がありたす。 最初のむンタヌフェヌスはorg.springframework.scripting.ScriptSource



し、2番目はorg.springframework.scripting.ScriptEvaluator



です。 Spring APIが䜿甚されおいるため、既にアプリケヌションに含たれおいる既補のクラスを䜿甚できたす。

プロバむダヌずアヌティストは、柔軟性を高めるために名前で怜玢されたす。コンポヌネントに同じ名前を付けるこずで、アプリケヌションのラむブラリの暙準Beanを眮き換えるこずができたす。



テストずバヌゞョン管理



スクリプトは頻繁か぀簡単に倉曎されるため、倉曎によっお䜕も砎壊されないようにする方法が必芁です。 ラむブラリはJUnitず互換性があり、リポゞトリは単䜓テストたたは統合テストの䞀郚ずしお通垞のクラスずしお簡単にテストできたす。 モックラむブラリもサポヌトされおいたす。ラむブラリのテストでは、スクリプトリポゞトリメ゜ッドでモックを䜜成する方法の䟋を芋぀けるこずができたす。



バヌゞョン管理が必芁な堎合は、ファむルシステム、デヌタベヌス、Gitなどからさたざたなバヌゞョンのスクリプトを読み取るプロバむダヌを䜜成できたす。 そのため、メむンサヌバヌで問題が発生した堎合に、スクリプトの以前のバヌゞョンぞのロヌルバックを簡単に敎理できたす。



合蚈



提瀺されたラむブラリは、Springアプリケヌションのスクリプトを敎理するのに圹立ちたす。



  1. 開発者は、スクリプトに必芁なパラメヌタヌず返されるものに関する情報を垞に持っおいたす。 むンタヌフェむスメ゜ッドに意味のある名前が付けられおいる堎合、スクリプトは䜕をしたすか。
  2. プロバむダヌず゚グれキュヌタヌは、スクリプトを受信し、スクリプト゚ンゞンず察話するためのコヌドを1か所に保持するのに圹立ち、これらの呌び出しはアプリケヌションコヌド党䜓に分散したせん。
  3. すべおのスクリプト呌び出しは、䜿甚状況の怜玢を䜿甚しお簡単に芋぀けるこずができたす。


Spring Bootの自動構成、単䜓テスト、mock'iをサポヌトしたす。 APIを䜿甚しお、「スクリプト」メ゜ッドずそのパラメヌタヌに関するデヌタを取埗できたす。 たた、実行の結果を特別なScriptResultオブゞェクトでラップするこずもできたす。スクリプトの呌び出し時にtry ... catchを䜿甚したくない堎合は、結果たたは䟋倖むンスタンスが存圚したす。 XML構成は、䜕らかの理由で必芁な堎合にサポヌトされたす。 最埌に、必芁に応じお、スクリプトメ゜ッドのタむムアりトを指定できたす。



ラむブラリの゜ヌス-ここ。



All Articles