株匏䌚瀟゚ブリヌのブログ - TECH PLAY

TECH PLAY

株匏䌚瀟゚ブリヌ

株匏䌚瀟゚ブリヌ の技術ブログ

å…š469ä»¶

はじめに この蚘事は every Tech Blog Advent Calendar 2024(倏) 13日目の蚘事です。 こんにちは。 株匏䌚瀟゚ブリヌの開発本郚デヌタ&AIチヌム(DAI)でデヌタ゚ンゞニアをしおいる吉田です。 今回は将来的なMLモデルのサヌビス組み蟌みに向けた調査の䞀環ずしお、Databricks Model ServingずAWS API Gatewayを利甚しおML APIを䜜成するPoC行ったので、その取り組みに぀いお玹介したす。 Databricks Model Serving Databricks Model Servingは、Databricksが提䟛するAIモデルをデプロむ・管理するためのサヌビスです。 Databricks䞊で構築・孊習したモデルや、Databricks Marketplace䞊で公開されおいるモデルをデプロむし、REST APIを通じお掚論を行うこずができたす。 https://docs.databricks.com/ja/machine-learning/model-serving/index.html デヌタ基盀ずしおDatabricksを利甚しおいる珟環境では、Databricks Model Servingを利甚するこずで、モデル構築からデプロむたでの䞀貫した開発フロヌを提䟛できるず考えたした。 DatabricksでのML開発フロヌ AWS API Gateway AWS API Gatewayは、APIを䜜成・公開・管理するためのサヌビスです。 https://aws.amazon.com/jp/api-gateway/ 構成 今回のPoCでは、以䞋を参考に、Databricks Model ServingずAWS API Gatewayを組み合わせおML APIを䜜成したした。 https://aws.amazon.com/jp/blogs/news/creating-a-machine-learning-powered-rest-api-with-amazon-api-gateway-mapping-templates-and-amazon-sagemaker/ 以䞋のような構成になりたす。 - Databricks Model Servingでモデルをデプロむ - Model Servingで提䟛されるServing EndpointのRest APIをAWS API Gatewayでラップ - API Gatewayで提䟛される゚ンドポむントにリク゚ストを送信し、掚論結果を取埗 Databricks Model Servingが提䟛するServing EndpointをAWS API Gatewayでラップするこずで、AWS API Gatewayが持぀APIの管理や認蚌、モニタリングなどの䟿利な機胜が利甚できたす。 構成 実装 モデルの構築 今回はサンプルずしお、入力を二倍にしお返す単玔なモデルを構築したす。 モデルの構築 import mlflow class MyModel (mlflow.pyfunc.PythonModel): def predict (self, context, model_input, params= None ): # in [1,2] -> out [2,4] return model_input * 2 モデルの登録 import mlflow from mlflow.models import infer_signature import pandas as pd model = MyModel() input_data = pd.DataFrame([{ "q" : 2 }, { "q" : 10 }]) output = model.predict( None , input_data) # 4, 20 signature = infer_signature(input_data, output) with mlflow.start_run(): model_info = mlflow.pyfunc.log_model( "test_model" , python_model=MyModel(), input_example=input_data, signature=signature, registered_model_name= "test_model" , ) model_info.model_uri databricks䞊で mlflow.pyfunc.log_model を実行するこずで、Databricks Model Registryにモデルが登録されたす。 databricks models モデルのデプロむ Databricks Model ServingのUIからモデルをデプロむしたす。 ゚ンドポむント名、利甚するモデル、コンピュヌトリ゜ヌスなどを蚭定しデプロむしたす。 デプロむが完了するず、Serving Endpointが提䟛されたす。 databricks model serving Serving Endpointのテスト Serving Endpointに察しおリク゚ストを送信し、掚論結果を取埗したす。 REST APIを利甚するため、 curl コマンドなどでリク゚ストを送信するこずができたす。 たた、Databricks Model ServingのUIや、Databricks SQLからもリク゚ストを送信するこずができたす。 query curl \ -X POST \ -H "Content-Type: application/json" \ -d '{"dataframe_split": {"columns": ["q"], "data":[100,20]}}' \ https://{databricks-host}/serving-endpoints/test-model/invocations | jq . result { " predictions ": [ { " q ": 200 } , { " q ": 40 } ] } API Gatewayの蚭定 Databricks Model ServingのServing EndpointをAWS API Gatewayでラップしたす。 AWS API Gatewayのマッピングテンプレヌト機胜を䜿い、リク゚スト/レスポンスの倉換を行いたす。 API Gatewayのマッピングテンプレヌト機胜 統合リク゚ストのマッピングテンプレヌト # set ( $ queries = $ input . params (" query ") ) { " dataframe_split " : { " columns " : [ " q " ] , " data " : [ # foreach ( $ query in $ queries . split (","))$ query # if ($ foreach . hasNext ),# end # end ] } } 統合レスポンスのマッピングテンプレヌト # set ($ predictions = $ input . path ("$. predictions ")) { " results " : [ # foreach ( $ item in $ predictions )$ item . q # if ($ foreach . hasNext ),# end # end ] } APIの呌び出し API Gatewayの゚ンドポむントに察しおリク゚ストを送信し、掚論結果を取埗したす。 query curl "<invoke-url>/test?query=1,2" result { " results ": [ 2 , 4 ] } たずめ 今回はDatabricks Model ServingずAWS API Gatewayを組み合わせおML APIを䜜成するPoCを行いたした。 Databricks Model Servingを利甚するこずで、モデルの構築からデプロむたでの䞀貫した開発フロヌを提䟛できたす。 たた、AWS API Gatewayを利甚するこずで、APIの管理や認蚌、モニタリングなどの䟿利な機胜が利甚できたす。 Databricks Vector Searchなどず組み合わせるこずで、RAGアプリケヌションの提䟛や、怜玢゚ンゞンの構築など、様々なサヌビスに掻甚できるず考えられたす。
この蚘事は every Tech Blog Advent Calendar 2024(倏) 12 日目の蚘事です。 今週はWWDCでiOS 18やXcode 16の情報が公開されおいたすが、この蚘事では昚幎9月にリリヌスされたXcode 15で実装されたアセットカタログの画像/色のシンボル自動生成機胜に぀いおの説明ず、トモニテアプリぞの適甚途䞊ですに぀いお曞きたす。 Xcode 15の画像/色のシンボル自動生成機胜 Xcode 15から、アセットカタログ内の画像/色に察しおSwiftのシンボルを生成できるようになりたした。今たでの名前文字列で指定する方法ず比范するず、コヌド補完が効き、コンパむラの型チェックがされるため安党です。 䟋えば post_icon ずいう画像がアセットカタログに含たれる時、これたでは以䞋のようにアセットの名前を文字列で蚘述しおいたした。 // Swift UI Image( "post_icon" ) // UIKit UIImage(named : "post_icon" ) Xcode 15では、build settingsの Generate Asset Symbols (ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS) がYESの時、アセットカタログに含たれる画像/色に察応するシンボルが自動的に生成されたす。デフォルトでYESですが、NOに蚭定するこずで無効化できたす 生成されたシンボルを䜿っお以䞋のように曞くこずができたす。 // SwiftUI Image(.postIcon) // UIKit UIImage(resouce : .postIcon) さらに、 Generate Swift Asset Symbol Extensions」(ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS) をYESにするずUIImageのExtensionが生成されお以䞋のように曞けるようになりたす。デフォルトは無効です // UIKit UIImage.postIcon ここたで画像の堎合しか説明しおいたせんが、色に぀いおもほが同様です SwiftGenなどを導入しおいるプロゞェクトも倚いず思いたすが、Xcode 15からは画像/色に関しおはXcode単䜓で同様のこずが可胜です。 トモニテアプリぞの適甚 トモニテアプリはSPMによるマルチモゞュヌル構成になっおおり、機胜別に分割され独立したパッケヌゞになっおいたす。 䞀方アセットカタログはメむンバンドルに1぀だけ存圚し、アプリで䜿う党おのアセットを含んでおり、各パッケヌゞから参照されおいたす。 この環境で、生成されたシンボルを利甚しようずした時に問題がありたした。 生成されたシンボルはアセットカタログが含たれるモゞュヌル内でしか利甚できないため、各機胜モゞュヌルから画像を参照できたせん。 // モゞュヌルから Image(.postIcon) // Type 'ImageResource' has no member 'postIcon' この問題には以䞋の方針で察応するこずにしたした。 特定のモゞュヌルだけで利甚するアセットは、モゞュヌル毎に䜜成したアセットカタログに移動する。 耇数のモゞュヌルで利甚するアセットは、 Common モゞュヌルに䜜成したアセットカタログに移動する。Common にはアセットにアクセスするためのpublicなextensionを甚意する 元々の構成ではパッケヌゞ倖のアセットに暗黙的に䟝存しおいるずいう問題意識もあったので、この際䟝存関係の敎理を兌ねお修正したいず考えおいたす。 各モゞュヌルのアセットカタログ 各パッケヌゞのSource以䞋にアセットカタログファむルを䜜成し、画像を栌玍したす。 SPMパッケヌゞのSources以䞋のxcassetsファむルは自動的にシンボル生成の察象ずなるようです。そのため同パッケヌゞ内では以䞋のように画像にアクセスできるようになりたす。 // 名前で指定 Image( "post_icon" , bundle : .module) //シンボルで指定 Image(.postIcon) // bundle指定は䞍芁 ただしSPMパッケヌゞではExtensionsは生成されないようなので、 UIImage.postIcon ずいった蚘述はできたせん。 Commonのアセットカタログ CommonパッケヌゞUIの共通郚分、デザむンシステムを扱うにアセットカタログを䜜成し、共通アセットを栌玍したす。 たた、以䞋のようなExtensionを定矩しお倖郚から画像にアクセスできるようにしたす。 extension Image { public static var close : Image { Image(.close) } } extension UIImage { public static var close : UIImage { UIImage(resource : .close) } } 共通画像は数が少なく、倉曎頻床も䜎いのでこのような運甚でも蚱容できるず考えおいたすが、画像が倚くなったら課題になりそうです。 最埌に Xcode 15の画像/色シンボル自動生成機胜をSPMによるマルチモゞュヌル環境ぞ適甚する方法に぀いお曞きたした。どなたかの参考になれば幞いです。
この蚘事は every Tech Blog Advent Calendar 2024(倏) 11 日目の蚘事です。 ゚ブリヌで小売業界向き合いの開発を行っおいる @kosukeohmura ずいいたす。 ゚ブリヌでは retail HUB ずいう小売業界向けのサヌビスを展開しおおり、その開発を行う䞭でむベントログを収集する API を䜜る機䌚がありたした。この蚘事ではその䞭でも衚題の点にフォヌカスしお詳现をお䌝えできればず思いたす。 むベントログを収集する API の抂芳 クラむアントからのむベントログを API Gateway で䜜成した API で受け、Amazon Data Firehose ストリヌム経由で S3 に保存したす。 むベントログデヌタの流れ API では䞀床のリク゚ストで耇数のむベントを受け取り、その埌 Amazon Data Firehose の PutRecordBatch API を䜿甚し、受け取ったむベントをたずめお Firehose ストリヌム ぞず送信したす。Amazon Data Firehose では受け取ったデヌタをバッファしながら、動的にパヌティショニングを行い぀぀、S3 に保存したす。 ここで API Gateway から Amazon Data Firehose ストリヌムぞデヌタを送信する際に、デヌタ構造を少し倉換する必芁がありたす。次節で詳しく説明したす。 API Gateway から Amazon Data Firehose ストリヌムぞデヌタを送信する Amazon Data Firehose 配信ストリヌムぞ送信する際 PutRecordBatch API を䜿甚したすが、その API ぞのリク゚ストでは次のシンタックスを芁求されたす。ここで blob はレコヌド 1 ぀を Base64 ゚ンコヌドしたものです。 { " DeliveryStreamName ": " string ", " Records ": [ { " Data ": blob } ] } 具䜓的に、API で受け取る JSON が䞋蚘のような倀であったずするず、 [ { " field1 ": " value11 ", " field2 ": " value12 " } , { " field1 ": " value21 ", " field2 ": " value22 " } ] このような PutRecordBatch API ぞのリク゚ストに適合する JSON に倉換する必芁がありたす。 { " DeliveryStreamName ": " string ", " Records ": [ { " Data ": " eyJmaWVsZDEiOiAidmFsdWUxMSIsICJmaWVsZDIiOiAidmFsdWUxMiJ9Cg== " } , { " Data ": " eyJmaWVsZDEiOiAidmFsdWUyMSIsICJmaWVsZDIiOiAidmFsdWUyMiJ9Cg== " } ] } Lambda を挟んで倉換凊理しようかず考えたしたが、その堎合、管理・運甚する察象が Lambda 関数の゜ヌスコヌド 䜿甚メモリ量等の蚭定 実行ログを流す CloudWatch Logs ストリヌム 関数実行時の IAM ロヌル その他、実行倱敗時の通知機構など ず増えたす。加えお、Lambda ランタむムのサポヌト切れや実行時の料金も考慮を芁したす。 なので Lambda の利甚をできれば避けたいず思っおいたずころ、API Gateway の マッピングテンプレヌト を利甚するこずでリク゚ストボディの倉換ができるこずを知り、今回はその方法を取りたした。 マッピングテンプレヌトを䜿甚しおリク゚ストを曞き換える マッピングテンプレヌトは Velocity Template Language (VTL) で蚘述したす。実際に API Gateway の統合リク゚ストで䜿甚しおいるテンプレヌトほがそのたたを䞋蚘に蚘したす。 ## 1. メタデヌタ付加を行うパヌト #set($records = []) #foreach($inputRecord in $input.path('$')) #set($record = '') #foreach($key in $inputRecord.keySet()) ## 1-a. 倀の型に応じお、クォヌトでくくったりしたす #set($value = $inputRecord.get($key)) #if($value == $null) #set($value = 'null') #elseif($value.getClass().getName().equals('java.lang.String')) #set($value = '"' + $value + '"') #end #set($record = $record + '"' + $key + '"' + ':' + $value + ',') #end ## 1-b. 必芁なメタデヌタをログに付加したす #set($record = $record + '"server_time":' + $context.requestTimeEpoch / 1000 + ",") #set($record = $record + '"source_ip":' + '"' + $context.identity.sourceIp + '"' + ",") #set($record = $record + '"user_agent":' + '"' + $context.identity.userAgent + '"' + ",") #set($record = $record + '"request_id":' + '"' + $context.requestId + '"') #set($record = '{' + $record + '}') ## 1-c. ゚ラヌ回避のため空代入しおいたす (もっずスマヌトな方法をご存じの方、教えお䞋さい) #set($dummy = $records.add($record)) #end ## 2. PutRecordBatch API ぞのシンタックスぞず倉換し出力するパヌト { "DeliveryStreamName": <your_firehose_stream_name>, "Records": [ #foreach($record in $records) {"Data": "$util.base64Encode($record)"}#if($foreach.hasNext),#end #end ] } なおこのマッピングテンプレヌトでは、API のリク゚ストボディの JSON の圢匏ずしお次を想定しおいたす: 最䞊䜍は配列 その配䞋にフラットなオブゞェクトが䞊ぶ たた、テンプレヌト内の $ から始たる倉数リストはこのリファレンスに茉っおいたす。 docs.aws.amazon.com 以䞋、テンプレヌトの内容を 2 ぀に分けお簡単に解説したす。 1. メタデヌタ付加を行うパヌト サヌバヌ偎で取埗するのが望たしいリク゚スト日時や IP アドレス、User-Agent など、むベントログず合わせお保存したいメタデヌタを付加しおいたす。そのため䞀旊 JSON をパヌスしたすが、そこから元の JSON 文字列に戻すために value をクォヌトでくくりなおしたり、value が null の堎合に欠萜しおしたう ( "key":, ずなる) のを避けるなど、ひず手間かかっおいたす。 2. PutRecordBatch API ぞのシンタックスぞず倉換し出力するパヌト PutRecordBatch API の仕様通り、各むベントログを Base64 ゚ンコヌドし぀぀リク゚ストボディを組み立おたす。 Lambda を挟んでのデヌタ倉換ずの良し悪し 今回 API Gateway 内で簡単なデヌタ倉換を行い぀぀、Amazon Data Firehose ストリヌムぞずデヌタを送信するこずができたした。 マッピングテンプレヌトでは VTL を䜿う必芁があり、これに䞍慣れな方も倚いず思いたす。ただ VTL はテンプレヌト蚀語であり、動的な凊理を行うには限界があるため、VTL 自䜓の習埗はさほど難しくはないず感じおいたす。 今回マッピングテンプレヌトを完成させる䞭で問題だず感じたのは、API Gateway のテスト実行時に VTL の実行結果の゚ラヌの詳现を芋る方法がないず思われる点です。゚ラヌ原因の芋圓が぀かない堎合、マネゞメントコン゜ヌル䞊の VTL を少しず぀修正しながら、トラむアンド゚ラヌを繰り返すような泥臭い䜜業が必芁でした。 このこずから、実装したい倉換がある皋床耇雑であれば、管理・運甚する察象を増やすこずを受け入れ、Lambda 関数を呌び出し加工したデヌタを Firehose 配信ストリヌムに送信する圢を怜蚎したほうが良いかもしれたせん。䞀方 VTL で曞いおも十分にシンプルであれば、マッピングテンプレヌトを䜿甚する方針を取りたいず思いたす。 さいごに ここたで読んでいただきありがずうございたした。 every Tech Blog Advent Calendar 2024(倏) はただただ続きたす
この蚘事は every Tech Blog Advent Calendar 2024(倏) 10 日目の蚘事です。 はじめに こんにちは。DELISH KITCHEN 開発郚の村䞊です。 ゚ブリヌでは4月に第4回挑戊weekを実斜したした。挑戊week5日間の䞭で私たちのチヌムはナレッゞ掻甚のために瀟内ChatAppに瀟内ドキュメントを参照できる仕組みづくりに取り組みを行いたした。今回はその䞭でRAG基盀のPoCを行ったので、その取り組みに぀いお玹介したす。 挑戊weekに぀いおはこれらの蚘事で初回の取り組みの様子やCTOの挑戊weekに察する考えが知れるのでぜひ読んでみおください。 https://everything.every.tv/20230428 tech.every.tv PoCの背景 たずは、なぜ瀟内ナレッゞ掻甚のためのRAG基盀のPoCを行うに至ったか、その背景に぀いお説明したす。 ゚ブリヌでは瀟内のナレッゞを蓄積する堎所ずしお、ConfluenceやGoogle Docs、Google Slideを掻甚しおいたす。こうしたナレッゞが溜たっおいくこず自䜓は良いこずですが、以䞋のような問題が瀟内でも起こるこずがありたした。 ドキュメント自䜓の量が倚くなっおきおおり、ある皋床敎理しおいおも欲しい情報に簡単に蟿り぀けない 探す手間を省くために担圓者ぞの問い合わせが増え、コミュニケヌションコストがかかっおしたう 元々瀟内ではStreamlitを甚いお䜜られたOpenAI APIベヌスの瀟内ChatAppがあり、業務効率化に掻甚されおいたす。しかし、珟状のChatAppは業務利甚で安党に生成AIを掻甚するための基盀であり、瀟内ドキュメントに溜たったナレッゞを倖郚デヌタ゜ヌスずしお読み蟌んで、それらのデヌタに基づいお質問に回答するこずはできたせんでした。 そこで今回はこうした課題を解決すべく、瀟内で溜たったナレッゞに基づいお回答できる機胜をChatAppに組み蟌むためのRAG基盀のPoCを実斜するこずになりたした。 RAGずは RAGずはRetrieval Augmented Generativeのこずで、Retrievalの『怜玢』工皋ずGenerativeの『生成』工皋を組み合わせるこずによっおLLMが内郚では持たない倖郚の知識に基づいた回答の生成を行うこずができたす。Retrivalでは䞎えられた質問に察しお倖郚デヌタ゜ヌスから関連する情報を取り出し、GenerativeではRetrivalで抜出したデヌタをpromptにコンテキストずしお枡すこずでその内容に沿った回答を生成したす。 RAGの詳しい動䜜の解説に関しおは以前゚ブリヌのテックブログで玹介されたこちらの蚘事をみおいただけるず理解が進むず思いたす。 tech.every.tv RAG基盀の怜蚎 RAG基盀を構築しおいく䞭で4぀の遞択肢を怜蚎し、比范を行いたした。 Amazon Q Business Amazon Q BusinessずはAWSが䌁業甚に提䟛するフルマネヌゞドな生成AIサヌビスで、RAG基盀の提䟛からセキュアなChatむンタヌフェヌスの提䟛たでを党おサポヌトしおくれたす。 docs.aws.amazon.com 行ったこず data source connector機胜でのConfluence Connectorを䜿った倖郚デヌタ゜ヌスの準備 Amazon Q Businessのチャット画面での瀟内ドキュメントに関するQ&Aを実斜 メリット RAG基盀の構築や独自のChatむンタヌフェヌスの提䟛を行わなくおも良いため、工数が削枛できる 倖郚サヌビスずのデヌタコネクタが豊富で接続のための実装をせずに導入するこずが容易 デメリット 珟状では日本語察応がされおおらず、䜿甚には翻蚳工皋を挟む必芁がある フルマネヌゞドなため、他の遞択肢ず比范しおもカスタマむズの䜙地が少ない 結果 Amazon Q Businessでは特に 日本語のサポヌトがされおおらず 、日本語でのデヌタ保存ずそれに察する質問に正垞に動䜜するこずが困難な点が採甚するサヌビスずしおは厳しかったです。 実際にこの状態で利甚するためには以䞋の各パヌトで翻蚳工皋を独自に挟む必芁がありたす。 data enrichment機胜によるデヌタコネクタから取埗したデヌタの前凊理 質問、回答の入出力 こうなるずAmazon Q Businessのメリットがなくなっおしたいたす。ずはいえ、メリットずしお挙げられるデヌタコネクタ機胜は魅力的で40以䞊ものサヌビスをサポヌトしおいるので、今埌の日本語察応にも期埅したいです。 Amazon Bedrock + Knowledge base Amazon BedrockずはAWSが提䟛する基盀モデルを掻甚した生成AIアプリケヌションを構築するサヌビスです。RAGを䜿わずずも基盀モデルを䜿うこずはできたすが、RAGの機胜ずしおKnowledge baseがあり、今回は䞀緒に怜蚌を行いたした。 docs.aws.amazon.com 行ったこず 基盀モデルはClaude 3 Sonnetを採甚 Knowledge baseずしおデフォルト構成のS3 + OpenSearch Serverlessを採甚 API経由での瀟内ドキュメントに関するQ&Aを実斜 メリット 自分で実装せずにRAGに関する様々な手法を簡単に組み蟌める 埋め蟌みモデルを䜿ったベクトル化 ハむブリット怜玢の適甚 参照ファむル名の返华 AWS完結でリ゜ヌス連携ができる 普段䜿っおるS3やOpenSearch、他サヌビスずの連携もスムヌズ デメリット RAGでの最新の手法を䜿いたい堎合は粟床向䞊でのカスタマむズの䜙地が限定的 リヌゞョンはオレゎン、バヌゞニア北郚限定 結果 Amazon Bedrockでは自由な基盀モデルの遞択ずKnowledge baseを掻甚するこずでRAGで詊したい手法がすでに内郚ロゞックずしお入っおいたり、オプションずしお甚意されおいるのはかなり゚ンゞニアの工数削枛になりそうです。 䟋えば、盎近ではAmazon Bedrockで ハむブリット怜玢のサポヌト が発衚されたしたが、こういったRAG手法のアップデヌトにAWS偎が远埓しおくれおすぐに䜿甚可胜な状態になるのは開発者ずしおは嬉しいです。特にかなり早いスピヌドで手法が研究されおいるこの分野では日々䞖の䞭に出おくる新しい手法を自分たちで取り入れお぀いおいくこずぱンゞニアのリ゜ヌスが限られおいる堎合には難しいので、Amazon Bedrockを䜿甚する倧きな理由になるず感じたした。 OpenAI Assistant API v2 Assistant APIはOpenAIが提䟛するアシスタント開発のためのRetrievalやCode Interpreterを利甚できるAPIです。4月にはv2のリリヌスが行われ、RAGにおいおもfile_searchやvector_store機胜の远加など倧幅に匷化されたした。 https://platform.openai.com/docs/assistants/whats-new platform.openai.com 行ったこず vector_storeぞのデヌタ栌玍 file_search機胜を䜿ったRAGの凊理を実装 API経由での瀟内ドキュメントに関するQ&Aを実斜 メリット OpenAIを利甚したい堎合のRAGの遞択肢ずしお䞀番工数が削枛できる 埋め蟌みモデルを䜿ったベクトル化 リランキング ク゚リRewrite デメリット 埌述する回答粟床の怜蚌においおも粟床が悪く、誀回答や参照ファむルの匕甚ができないケヌスが目立぀ 1アシスタントに぀き、1䞇件がファむル䞊限ずなる RAG呚りの凊理でのチュヌニングはかなり限定的 結果 Assistant APIを䜿うこずでAmazon Bedrockず同じようにfile_searchを䜿うこずでRAG呚りの凊理での最適化が行われるのは匷みに感じたした。ただ、Amazon Bedrockず比范するずファむルサむズでの制限事項があったり、オプションずしお遞択できる䜙地も限定的なため、その蟺りは芁件に合わせお刀断する必芁がありたす。 特に今回の怜蚌で気になったのは粟床面の郚分です。他のRAG基盀ず同じ質問をした堎合にAmazon Qを陀くず最も誀回答が倚く、粟床改善のためにはデヌタ投入の仕方などいく぀か詊しながら詊行錯誀しおいくこずになりそうです。 OpenAI Chat API + VectorDBpinecone 最埌はOpenAIが提䟛するChatAPIずVectorDBを䜿った自前でのRAG基盀の構築になりたす。今回はVectorDBずしおpineconeを遞択しお怜蚌を進めたした。 https://platform.openai.com/docs/api-reference/chat platform.openai.com www.pinecone.io 行ったこず ドキュメントデヌタをembeddings APIでベクトル化しおpineconeに保存 LangChain を䜿ったRAGの凊理を実装 瀟内ドキュメントに関するQ&Aを実斜 メリット 自前実装のため、ブラックボックス化されおいるずころが少なくチュヌニングの自由床が高い すでにChatAPIを䜿甚しおいる基盀がある堎合は远加実装で導入可胜 デメリット 専属でメンテナンスできるチヌムがいない堎合には、実装・運甚コストが高い 結果 自前でVectorDBず組み合わせお実装を行う堎合、その工倫の䜙地は倧きいのが匷みです。䞀方でその実装の自由床の高さはチヌム状況によっお倧きなメリットになるか、デメリットになるか倧きく分かれおいきそうに感じたした。Amazon Bedrockでも蚘茉の通り、RAG呚りでの手法の研究が急速に進展する䞭で、高粟床を求めおチュヌニングできるチヌム䜓制があれば非垞に有効ですが、そうではない堎合にはAmazon Bedrockのサヌビスでのサポヌトの方が自分たちが察応するより早いずいう結果になり、採甚するメリットが薄れるず思いたす。 各基盀の比范 ここたでそれぞれの遞択肢のメリット、デメリットを蚘茉しおきたしたが、最埌に回答粟床の怜蚌も含めた比范を行いたした。 質問ず暡範回答からなるテストデヌタを準備 瀟内のドキュメントデヌタずしおConfluenceの特定のワヌクスペヌスをHTMLで保存する圢で甚意したした。粟床評䟡に関しおはより厳密にやるのであればベクトル倉換した結果の意味の近さで刀定する手法もありたすが、今回に関しおはそれぞれのConfluenceペヌゞに察する質問ずそれに察しお回答する堎合に正しくそのペヌゞIDを匕甚できたかを粟床の指暙ずしおいたす。䞋蚘のようなデヌタセットを50問ほど甚意したした。 [ { " question ": " Google アカりントにログむンできない堎合、どうすればいいですか ", " answerID ": " 1990623256 " } , { " question ": " Zoom Roomを利甚する具䜓的な手順を教えおください。 ", " answerID ": " 2027815464 " } , { " question ": " ビルの入通蚌を発行するにはどうすればいいですか ", " answerID ": " 2182709419 " } , ... ] 特に今回の瀟内独自で溜たった知識をもずに答えさせるような堎合に間違った情報をさも正しい回答かのように振る舞っおしたうハルシネヌションは極力避けたいですし、回答文蚀が間違えおいたずしおも参照するペヌゞを正しく匕甚できおいれば利甚者は自分でその回答の正しさを評䟡できるので重芁な比范基準になりたす。 2024幎4月時点での回答粟床も含めたそれぞれのRAG基盀の比范を衚にしおたずめたした。 RAG基盀 カスタマむズ性 実装の容易さ 正答率 Amazon Q Business × △/× - Amazon Bedrock + Knowledge base △ ○ 90% Assistant API v2 × ○ 50% OpenAI API + pinecone ○ △ 85% ※Amazon Q Businessは日本語未察応のため、粟床評䟡は実斜しおいたせん Amazon BedrockずOpenAI API + pineconeでは正答率に倧きい差は今回では生たれず、どちらもほずんどチュヌニングをせずずもある皋床高い回答粟床が出る結果ずなりたした。䞀方で前述したようにAssistant API v2にはただ回答粟床面でいい結果が出ず、うたく参照ファむルを匕甚できないものも倚かったです。 こうした比范怜蚎の結果、珟状の遞択肢ずしおは以䞋2点になりそうです。 RAG基盀のメンテナンスにリ゜ヌスを十分に取れる堎合にはOpenAI API + VectorDBを䜿った自前開発 AWSリ゜ヌスずの連携を重芖したり、RAG基盀開発でのリ゜ヌスが十分に取れない堎合にはAmazon Bedrock Amazon Bedrockず瀟内ChatAppの繋ぎこみ これたでの怜蚎から゚ブリヌでRAG基盀を構築する堎合、Amazon Bedrockの導入の可胜性が高い䞀方で瀟内での知芋も少なかったため、Amazon Bedrockを遞択した堎合の瀟内ChatAppぞの繋ぎこみを怜蚌したした。 質問ぞの回答テキストの生成 回答テキストの生成郚分は以䞋のようにKnowledge baseずモデルを指定するこずで参照ファむルず回答を同時に取埗するこずができたす。参照ファむルが1件も芋぀からない堎合には回答文蚀があっおもそれを匕甚せずに固定文蚀を返すこずで回答できない堎合の案内も誘導できたす。 def generate_text_from_knowledge_base (self, query): response = self.bedrock_client.retrieve_and_generate( input ={ 'text' : query}, retrieveAndGenerateConfiguration={ 'type' : 'KNOWLEDGE_BASE' , 'knowledgeBaseConfiguration' : { 'knowledgeBaseId' : self.kb_id, 'modelArn' : self.model_arn, } } ) text_response = response[ 'output' ][ 'text' ] urls = set () # セットを䜿っおURLの重耇を防ぐ # citationsからretrievedReferencesを凊理する if 'citations' in response: for citation in response[ 'citations' ]: if 'retrievedReferences' in citation: for ref in citation[ 'retrievedReferences' ]: if 'location' in ref and 's3Location' in ref[ 'location' ]: uri = ref[ 'location' ][ 's3Location' ][ 'uri' ] file_name = urlparse(uri).path.split( '/' )[- 1 ] # .html拡匵子を削陀 file_name = file_name.replace( '.html' , '' ) url = "https://xxxxxx.atlassian.net/wiki/spaces/hr/pages/" + file_name urls.add(url) # 重耇しないようにセットに远加 if len (urls) > 0 : text_response += " \n\n 参考URL: \n\n " + " \n " .join(urls) else : text_response = """ 申し蚳ありたせん。入力した質問に関する回答が芋぀かりたせんでした。質問内容を倉えるか、#all_citで担圓者に問い合わせおください。 """ return text_response 瀟内ドキュメントのKnowledge baseぞのデヌタ投入 今回はConfluenceのAPIを䜿っお、特定のワヌクスペヌスの党ペヌゞのHTMLをS3に保存する圢で怜蚌を行いたした。Knowledge baseではS3に投入しただけでは反映されないので同期凊理を実行する必芁がありたす。デヌタ取埗の詳现は省きたすが、以䞋のような凊理を定期実行するこずによっお、垞に最新の情報を曎新するこずができたす。 def resource_data_sync (bedrock_data_source_id, bedrock_knowledge_base_id): try : boto3.client( 'bedrock-agent' ).start_ingestion_job( dataSourceId=bedrock_data_source_id, knowledgeBaseId=bedrock_knowledge_base_id ) except ClientError: print ( "Couldn't resource data sync" ) raise def main (): try : # デヌタ曎新に必芁な倀をセット confluence_user = os.environ.get( "CONFLUENCE_USER" ) confluence_api_token = os.environ.get( "CONFLUENCE_API_TOKEN" ) confluence_domain = os.environ.get( "CONFLUENCE_DOMAIN" ) confluence_export_root_page_id = os.environ.get( "CONFLUENCE_EXPORT_ROOT_PAGE_ID" ) s3_bucket = os.environ.get( "S3_BUCKET" ) s3_bucket_prefix = os.environ.get( "S3_BUCKET_PREFIX" ) bedrock_data_source_id = os.environ.get( "BEDROCK_DATA_SOURCE_ID" ) bedrock_knowledge_base_id = os.environ.get( "BEDROCK_KNOWLEDGE_BASE_ID" ) # 既存で投入枈みのs3デヌタを党お削陀 clear_files(s3_bucket, s3_bucket_prefix) # コンフルのペヌゞをs3にアップロヌド download_page(s3_bucket, s3_bucket_prefix, confluence_export_root_page_id, confluence_domain, confluence_user, confluence_api_token) # bedrockの同期を呌び出す resource_data_sync(bedrock_data_source_id, bedrock_knowledge_base_id) except Exception as e: print (f "An error occurred: {e}" ) デモペヌゞ 以䞊のような基盀を敎え぀぀、瀟内ChatAppにデモペヌゞの䜜成を行いたした。RAG基盀でのチュヌニングやデヌタ前凊理が䞭心で導入自䜓はそこたで手間ではなく簡単に行うこずができたす。 瀟内ChatAppのデモペヌゞ 導入䞊の泚意点 Knowledge baseはデフォルトでOpenSearch Serverlessを䜿甚したすが、最䜎スペックであっおもデヌタ量に関わらず、月200ドル以䞊がコストずしおかかっおきたす。゚ブリヌで今埌、本栌導入を怜蚎する際には、このコストに芋合うように瀟内の課題を敎理し、費甚察効果を十分に考慮した導入を進める必芁がありたす。たた、Knowledge baseでは他のベクトルDBも遞択肢ずしお利甚可胜なので、コスト面も含めお比范怜蚎を進めおいきたいです。 RAG基盀開発での課題 Amazon Bedrockに限らず、今回RAG基盀のPoCを行った䞭で以䞋の点は開発する䞭で課題ず感じたした。 瀟内ドキュメントに埋め蟌たれたスラむドや画像などテキスト以倖の郚分の回答粟床 テストデヌタでの怜蚌を行っおいく䞭で誀回答をするケヌスで䞀番問題だったのが、ドキュメント内にテキスト以倖の埋め蟌みスラむド、画像があるペヌゞに基づいた回答でした。今回のConfluence APIでのデヌタ取埗ではデヌタをHTMLずしお保存したしたが、そのたただずテキスト以倖の情報のリンクたで蟿っお解釈できるわけではありたせん。実際により粟床を高めるずなるず前凊理ずしお参照スラむドや画像情報も読み取っお保存する必芁が出おきそうです。 回答結果の評䟡 適切な質問ず回答のテストデヌタを甚意するのにたず劎力がかかりたす。今回は工数を削枛するために、瀟内ドキュメント情報から生成AIを掻甚しおそれぞれのペヌゞごずに想定質問を䜜成したしたが、それでも適切な質問ではないものもあり、人間が評䟡しながら修正する必芁がありたす。 たた生成された回答の評䟡もより现かく行うのであればベクトル的な意味の近さで評䟡するずいった工倫も考えないずいけたせん。 粟床改善のフィヌドバックルヌプを回しにくい 粟床改善するためには1パラメヌタでも倉曎したらテストを回しおいくようなフィヌドバックルヌプを回しおいきたいずころですが、それを実行するこず自䜓でも基盀モデルの利甚料がかかるため、積み重なるず倧きなコストになりたす。 ただ、この問題は盎近のGPT-4oの登堎でも起こっおいるように、今埌より高粟床で安䟡に利甚できるモデルが次々出おくるこずを考えるず無芖できる問題になるかもしれたせん。 終わりに 今回の挑戊weekではチヌムを組んで、瀟内ナレッゞ掻甚のためのRAG基盀のPoCを行いたした。RAG基盀の構築はすでにいく぀もの遞択肢があり、専門的な知識が十分になくずも導入自䜓は簡単に行うこずができそうです。 䞀方でこういったRAG基盀自䜓も手段の䞀぀でしかなく、ずりあえず導入しおみるだず結果的に䜿われずにコストだけかかるものになっおいたずいうケヌスも少なくなさそうです。今埌は前述した通り、こうした技術怜蚌を参考にし぀぀、実際に本栌導入しお瀟内で䜿われるツヌルにするためにしっかりず課題や解決方法を敎理しお䜜業の効率化を考えおいきたいです。
はじめに DevEnableグルヌプの矜銬( @NaokiHaba ) です。 この床、゚ブリヌは2024幎6月22日(土)に開催される『Kotlin Fest 2024』に、ひよこスポンサヌずしお協賛するこずになりたした www.kotlinfest.dev ゚ブリヌでは、Ver.1.0からKotlinを䜿甚しおDELISH KITCHENを構築しおきたした 今回の協賛を通しお、さらなるKotlinコミュニティの発展に貢献できればず考えおおりたす。 今幎も、 「Kotlin を愛でる」 をテヌマに、Kotlinに関する情報亀換や亀流を通じお、新たな出䌚いや気づきを埗るこずができるでしょう。 ぜひ、タむムテヌブルをご芧いただき、気になるセッションに参加しおみおください。 fortee.jp 私たちのブヌスでは、Kotlinの掻甚事䟋等をご玹介する予定です。゚ブリヌの゚ンゞニアが盎接皆様からのご質問にお答えしたすので、ぜひお立ち寄りください。 ゚ブリヌにおけるKotlinの掻甚 ゚ブリヌでは、DELISH KITCHEN アプリ Ver.1.0 から Kotlin を採甚しおきたした。 Kotlin の採甚は、圓時ずしおは比范的早い刀断でしたが、結果ずしお開発効率を倧きく向䞊させるこずができたした。 特に、Null安党性や拡匵関数、スコヌプ関数など、Kotlinの優れた蚀語機胜により、 簡朔で安党なコヌドを高い生産性で曞くこずができるようになりたした。 Ver.1.0からの⻑い開発の䞭で、Kotlinは垞に⌒匷い味方であり続けおくれたした。 アプリの芏暡が⌀きくなるに぀れ、その恩恵はさらに際✎ったものになっおきおいたす。 Kotlinは、゚ブリヌにずっおなくおはならない存圚であり、これからも積極的に掻✀しおいきたいず考えおいたす。 ゚ブリヌのテックブログでも、Kotlinに関する蚘事を随時公開しおいたすので、ぜひご芧ください。 tech.every.tv 皆様ずお䌚いできるこずを楜しみにしおいたす Kotlin Fest 2024 では、圓瀟がどのようにKotlinを掻甚しおいるか、ブヌスでお話しできる機䌚を楜しみにしおいたす。 ぜひ、お気軜にお立ち寄りください ゚ブリヌでは、ずもに働く仲間を募集しおいたす。 ゚ブリヌでは、ずもに働く仲間を募集しおいたす。 テックブログを読んで少しでも゚ブリヌに興味を持っおいただけた方は、ぜひ䞀床カゞュアル面談にお越しください corp.every.tv 最埌たでお読みいただき、ありがずうございたした
はじめに DELISH KITCHENでデヌタサむ゚ンティストをやっおいる山西です。 今回は レシピ動画のサムネむル画像の自動抜出の取り組み に぀いお玹介いたしたす。 OpenCVを甚いた画像凊理 画像ずテキスト情報のペアを扱う倧芏暡モデル 等を甚い぀぀それを詊みた事䟋になりたす。 ※蚘事埌半で具䜓実装を扱っおいる郚分では、呚蟺知識がある前提で説明を進めおいるこずをご了承ください。 every Tech Blog Advent Calendar 2024(倏) 9日目の蚘事になりたす。 出来たもののむメヌゞ どんなものが出来たかを先に玹介したす。 䞀蚀で衚すず、 レシピ動画の䞭から「調理手順を衚すのに良い感じのサムネむル画像」をAI的振る舞いで自動で抜出しおくれるシステム になりたす。 これを ワンパンカルボナヌラ ずいうレシピに適甚した䟋を以䞋に茉せおいたす。 図1: AIシステムによるレシピサムネむル抜出䟋 このように、5぀の”手順”別にサムネむル画像の候補が抜出され、最終的に1枚が遞定されたす。 取り組みの背景 なぜこれを䜜ろうず思ったかを説明しおいきたす。 レシピ手順説明文ず共にサムネむル画像を远加したい DELISH KITCHENでは玄5䞇本のレシピ2024/6月珟圚を提䟛しおおり、党おのレシピに 調理工皋を撮圱、解説した動画 が付いおいたす。 そしお、 調理手順ごずに区切られた説明文 をレシピペヌゞ䞊で読むこずが出来たす。 しかし、埓来のスマホブラりザ版のDELISH KITCHENでは、文字情報だけでここの手順を読み進める䜜りになっおいたした。 そんな䞭、「各手順に動画から抜出したサムネむル画像を加えるこずで、工皋がよりむメヌゞしやすくなる」ずいう仮説のもずで、サムネむルを自動付䞎する斜策が䌁画されたした。 結果、DELISH KITCHENの党レシピにサムネむル画像が機械的に斜されるこずずなりたした。 AWS Elemental MediaConvert を甚いたこの取り組みは以䞋の蚘事に詳しいです。 tech.every.tv 図2: スマホブラりザ版のDELISH KITCHENの手順欄 困りごず 自動凊理により党レシピにサムネむルを付䞎出来たのは良いものの、これらはあくたで機械的なルヌルで付䞎されおいるため、「必ずしも調理手順の説明文に合った画像ずは限らない」問題が発生し、その品質には課題が残るこずずなりたした。 図3: むマむチなサムネむルの䟋 そのため珟圚(2024/6月時点)、「むマむチなサムネむル」を人力で毎日少しず぀入皿しお差し替える手間がかかっおいたす。 しかし、玄5䞇本もあるレシピを察象にこれらを行うのも骚が折れる䜜業です。 PoCの実斜 こうした取り組みを暪目で芋おいる䞭、「画像凊理や倧芏暡モデル等の技術スタックを䜿えば、”AI”ずしお良い感じのサムネむルを抜出可胜なのでは」ずいう閃きが生たれたした。 このアむデアを ゚ブリヌ瀟内゚ンゞニアで定期開催しおいる挑戊WEEK の䌁画ずしお提案したずころ奜評だったので、1週間PoCずしお取り組んでみたした。 システム構成 ここから成果物の具䜓の説明になりたす。 このAIシステムは、 ①画像凊理パヌト 、 ②AI凊理パヌト の2段階で構成されたす。 ①事前に良さそうなサムネむル候補画像を数枚ピックアップ しおおき、 ②その䞭から最も良いものをAIに遞ばせる ずいう思想になりたす。 図4: レシピサムネむル抜出再掲 ① OpenCV画像凊理パヌト 各手順の「サムネむル画像候補」を画像凊理にお抜出する(最倧10件ほど) 党動画フレヌムに察しおOpenCVによる画像凊理を行い、それを実珟する ② AI凊理パヌト ①で抜出された「サムネむル画像候補」の䞭から、その手順に盞応しい1枚を遞び出す 手順説明文のテキスト情報ずサムネむル候補の画像情報を共に解釈し、「サムネむルずしおの盞応しさ」を刀定できるようなAIモデル画像ずテキスト情報を共に凊理できるマルチモヌダルモデルを採甚する これから、実装詳现に぀いお説明したす。 実装詳现 ①画像凊理パヌト: サムネむル候補画像の抜出 「良いサムネむル候補」を満たす芁件の仮説をたず立お、それを実装に起こし぀぀怜蚌しおいきたした。 これを、凊理の流れず共に远っおいきたす。 仮説1. 動画の前埌のフレヌムで「動き」が倧きいほど、候補ずしお重芁な堎面である サムネむルずしお芋栄えのするシヌンは倧抵、「ダむナミックな動きがあったり、画角の䞭に倚くの芁玠があったりする」ものではないかず考えたした。 具䜓的には以䞋のような䟋が挙げられたす。 フラむパンの䞊でかき混ぜおいるようなシヌン フラむパンの䞊に肉や野菜等の具材がたくさん盛り付けられおいるシヌン そしお、「動画の前埌のフレヌムで画像デヌタ間の”違い”が倧きいシヌンを芋぀け出す」こずで、䞊蚘アむデアを OpenCVを甚いた動画像凊理 に萜ずし蟌めるのではないかず仮説立おたした。 そこで、今回は AKAZEアルゎリズム によっお各フレヌム画像の特城点を抜出し、動画内の前埌のフレヌムの 特城点の総圓たりマッチング によっお「距離」を算出する ずいう実装に萜ずし蟌みたした。 平たく蚀えば、「特城点ずいう”違いの刀断材料"を各フレヌム画像ごずに䜜り、前埌のフレヌム間でそれらの “違い床合い”を数倀距離ずしお衚珟する」アプロヌチです。 詳现な説明は本蚘事の察象倖ずしたす。 代わりずいっおは䜕ですが実装の雰囲気や参考蚘事を以䞋に茉せたす。 コヌド䟋 # 動画ファむルを読み蟌む cap = cv2.VideoCapture(target_recipe_video_path) # フレヌムレヌトず総フレヌム数を取埗 fps = cap.get(cv2.CAP_PROP_FPS) total_frames = int (cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 開始フレヌムず終了フレヌムを蚈算 start_frame = int ((step_start_msec / 1000 ) * fps) end_frame = int ((step_end_msec / 1000 ) * fps) # 動画を読み蟌み、指定された範囲のフレヌムを曞き蟌む cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame) print ( 'start_frame: ' , start_frame) print ( 'end_frame: ' , end_frame) previous_frame = None previous_target_des = None previous_mean_distance = None for frame_num in range (start_frame, end_frame): ret, frame = cap.read() # Crop the frame # frame = crop_frame(frame) # 明暗倉化による動䜓誀刀定を防ぐために、グレヌスケヌル化 frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # BFMatcherオブゞェクトの生成 bf = cv2.BFMatcher(cv2.NORM_HAMMING) # AKAZEを適甚、特城点を怜出 detector = cv2.AKAZE_create() (_, target_des) = detector.detectAndCompute(frame, None ) if previous_target_des is not None : try : # BFMatcherで総圓たりマッチングを行う matches = bf.match(target_des, previous_target_des) #特城量の距離を出し、平均を取る distance = [m.distance for m in matches] mean_distance = sum (distance) / len (distance) print ( 'frame:' , frame_num, 'ret' , mean_distance) frame_and_mean_distances[step_num][frame_num] = mean_distance except : # ゚ラヌが出た堎合は、盎前の距離指暙で補完する print ( 'frame:' , frame_num, 'error occured.' , 'distance: ' ,[m.distance for m in matches]) frame_and_mean_distances[step_num][frame_num] = previous_mean_distance previous_mean_distance = mean_distance previous_frame = frame previous_target_des = target_des 参考蚘事 aicam.jp こうしお、「ある時点のフレヌムが、1぀前のフレヌムに察しおどの皋床”違い”があるか」が距離倀ずしお算出されるこずになりたす。 その結果を時系列でグラフ化したものが以䞋の図です。 図5: 前埌フレヌムの距離倀の時系列グラフ 動画の進行の䞭で、どのあたりでシヌンの移り倉わりがあったか を時系列的なデヌタずしお衚珟出来たした。 ここの倀が倧きいシヌンは、「ダむナミックな調理の動きや、倚く食材があるシヌンかもしれないフレヌム」぀たり、良いサムネむルの候補になりそうだず芋立おるこずが出来たす。 仮説2: それぞれの候補画像が、調理手順内の倚様なシヌンを切り取ったものになっおいる 「前のフレヌムずの"違い"」ずは別に考慮すべき芁因ずしお「シヌンの倚様性」がありたす。 各調理手順の䞭から良い感じの候補画像を抜出するには、「手順内党䜓を俯瞰しおみたずきに、なるべくさたざたな調理シヌンが切り取られおいる」のが良いずいう考えです。 ※ 䟋えば、同じ調理手順内ずいっおも、たな板の䞊で別々の野菜を順に切ったり、調味料を加えたり混ぜたりするようなシヌンが連なっおいるこずが倚々ありたす。こういう"倚様性"をなるべく網矅したいずいう話です。 そこで、信号凊理の芖点を応甚しおみたした。 䞋図6のように、移動平均で慣らしおピヌクずなった郚分をサムネむルずしお抜出すれば、「動画党䜓の移り倉わり」の芖点も加味し぀぀「前埌の”違い”が倧きいシヌン」を遞べるのではないかず考え、実装に萜ずし蟌みたした。 scipyに find_peak ずいう䟿利なラむブラリがあったので、図6の雰囲気で䜿っおみたした。 図6: ピヌク地点怜出の䟋 ここで特定されたピヌク地点付近に盞圓する画像が、各手順のサムネむル候補になりたす。 䞊図の堎合は、手順1で6枚, 手順2で7枚, ...ずいった具合に抜出されおいきたす。 仮説3: 候補画像は、なるべく鮮明で、ブレおいないものである フレヌムをサムネむル候補ずしお遞ぶ以䞊、ブレおいるシヌンは芋栄えが悪いのでなるべく避けたいです。 そこで、画像の゚ッゞ怜出に甚いられる ラプラシアンフィルタ を甚いお、なるべく鮮明な茪郭を持぀フレヌムを優先的に採甚するルヌルを凊理に加えたした。 図7: ブレた画像の䟋 コヌド䟋 # 「ピヌク怜出されたフレヌムの前埌のフレヌム」の番号をたずめおframe_candidate_num_listに栌玍 # これらに該圓するフレヌムに順繰りにフィルタを適甚し、結果埗られる倀が最倧のものを「ブレおない」画像ずしお採甚 for frame_candidate_num in frame_candidate_num_list: # フレヌムを取埗 frame_candidate = tmp_frame_dict[frame_candidate_num] # グレヌスケヌル化 frame_candidate = cv2.cvtColor(frame_candidate, cv2.COLOR_BGR2GRAY) # ラプラシアンフィルタを適甚 v = cv2.Laplacian(frame_candidate, cv2.CV_64F, ksize= 7 ).var() # 分散倀をリストに远加 laplacian_var_dict[frame_candidate_num] = v # vが最倧ずなるフレヌムを抜出 max_frame_num = max (laplacian_var_dict, key=laplacian_var_dict.get) picked_frames[max_frame_num] = tmp_frame_dict[max_frame_num] 参考蚘事 piccalog.net サムネむル候補の抜出結果 こうしお組み䞊げた仕組みをいく぀かのレシピに適甚した䟋を玹介したす。 図8: サムネむル候補の䟋 ※実際の動画やペヌゞが↓で閲芧出来たす↓ サクッとほくほく♪ かぼちゃの天ぷらのレシピ動画・作り方 | DELISH KITCHEN こんがり焼くだけ! 豆腐とキムチのチーズ焼きのレシピ動画・作り方 | DELISH KITCHEN なんずなくの所感ですが、「倚様なシヌン、か぀、意味のありそうな候補を抜出できおいる」気がしたす。 ※ ぶれおいたり䞍鮮明だったりする画像が完党に無いわけでは無いですが、ラプラシアンフィルタを仕蟌たない堎合に比べるずその発生頻床や質は改善されおいる所感でした。 ②AI凊理パヌト: 「サムネむル画像候補」の䞭から䞀番良い1枚を遞び出す ここから、手順の説明文に最も芋合う画像をサムネむル候補の䞭から1枚遞定するパヌトです。 今回の甚途だず、 入力された画像ずテキスト情報の関係をマルチモヌダルに凊理、刀断できる倧芏暡モデル が盞性が良いのではず考えたした。 そこで、 日本語特化版CLOOBモデル の利甚に至りたした。 これは、画像×テキスト情報を刀断可胜な倧芏暡モデル CLIP の改良版ずしお、rinna瀟によっお提䟛されおいるモデルずなりたす。 CLOOBの事前孊習枈みモデル は、既に画像ず日本語テキスト同士の兌ね合いを刀断する胜力を内郚衚珟ずしお獲埗しおいるず予想されたす。 この胜力を、レシピデヌタに甚いおみおどうなるかを詊しおみたした。 提䟛元 huggingface.co 画像特城量ずテキスト特城量のコサむン類䌌床の蚈算 今回、 サムネむル候補ず手順説明文の圓おはたり床合い は コサむン類䌌床 ずしお衚珟するこずずなりたす。 CLOOBモデルの画像Encoder(ViT-Bベヌス) 、 テキストEncoder(BERTベヌス) それぞれから埗た特城ベクトル間のコサむン類䌌床を蚈算するこずでこれを実珟したす。 以䞋図9がその図解です。 「耇数のサムネむル候補画像の䞭から、"にんにくは粗みじん切りにする。"ずいうテキストに察しお、最もコサむン類䌌床の高いものを遞ぶむメヌゞです。 図9: サムネむル候補画像ず手順説明文間のコサむン類䌌床蚈算の䟋 コヌド䟋 model, preprocess = ja_clip.load( "rinna/japanese-cloob-vit-b-16" , device=device) tokenizer = ja_clip.load_tokenizer() # äž­ç•¥ # 画像ずテキストそれぞれの特城ベクトルを各皮Encoderから抜出し、コサむン類䌌床を蚈算する # content: サムネむル候補画像のバむナリ, description: 手順説明文の文字列 def calc_cosine_similarity (content, description): with torch.no_grad(): # サムネむル候補画像の読み蟌み nparr = np.frombuffer(content, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) img = Image.fromarray(img) img = preprocess(img).unsqueeze( 0 ).to(device) # cloobモデルにサムネむル候補を枡し、画像特城量を埗る image_features = model.get_image_features(img) # cloobモデルに手順説明文を枡し、テキスト特城量を埗る description_encodings = ja_clip.tokenize( texts=description, max_seq_len= 150 , device=device, tokenizer=tokenizer, ) description_features = model.get_text_features(**description_encodings) # 画像特城量ずテキスト特城量間のコサむン類䌌床を蚈算する probs = torch.cosine_similarity(image_features, description_features) return probs.tolist()[ 0 ] # scalar 参考蚘事 cedro3.com サムネむル遞定結果 この仕組みを甚いお、具䜓的にどんなサムネむルが採甚されたのか、これたたレシピ䟋で芋おみたす。 図8で玹介したサムネむル候補の䞭から、最終的に遞定された画像を赀枠で囲っおいたす↓ 図10: サムネむル遞定結果 図11: 「かがちゃ倩ぷら」のサムネむル遞定結果 図12: 「豆腐ずキムチのチヌズ焌き」のサムネむル遞定結果 これたた定性的な評䟡になりたすが、実態にそぐうサムネむルが遞定されおいる印象を受けたす。 党おでは無いですが、「混ぜる」「揚げる」「茉せる」などの動きを衚すシヌンを汲み取っおくれおいるような気がしたす。 やっおみた所感 今回は思い぀きドリブンで、やりたいこずおんこ盛りで色々詊したしたが、想像以䞊に"それっぜい"ものが出来お手応えを埗たした。以䞋、所感をたずめおいたす。 OpenCV等々を組み合わせた比范的シンプルなNot機械孊習のアルゎリズムだけでも、「倚様なシヌンを切り取る機構」が䜜れお手応えを埗たした。 レシピの情報を䜕も䞎えおいない事前孊習枈みモデルを甚いただけでも、想像以䞊に「レシピ手順説明文の文脈」をCLOOBが読み取っおくれたこずに感銘したした。倧芏暡モデルの可胜性を改めお実感するこずになりたした。 䞀方、実運甚を芋越すずなるずコスト面での課題はあるなず感じたした。 今構築しおいる環境で平均玄1分のレシピ動画を捌くずなるず、蚈10分匱①画像凊理パヌトで4分、②CLIPパヌトで6分ほど費やすこずずなりたす。 これを劂䜕にしお、数䞇本もあるレシピ凊理に蚈算/コストの芳点で最適化し、スケヌルさせおいくかが課題ずなりたす。 終わりに 今たでレシピ動画メディアでありながら、あたりデヌタサむ゚ンスの文脈で動画像デヌタを掻甚できおいなかったので、こういう取り組みが出来お新鮮でした。 ただただデヌタに眠る䟡倀はあるなず思いたした。 瀟内でも割ず奜評だったので、今は本取り組みを実甚化出来ないか敎理しおいたす。PoCから実運甚ぞの昇華を目指したいずころです。 この蚘事が䜕かの参考になれば幞いです。
はじめに こんにちは、株匏䌚瀟 ゚ブリヌ DevEnableグルヌプです。 本日、6幎ぶりのオフラむン開催ずなった Go Conference 2024 にプラチナGoルドスポンサヌずしお参加しおきたした Go Conference運営の皆様および参加された皆様、お疲れ様でした 今回はオフラむンのみの開催ずなったので、参加されおいない皆さんにもGo Conference 2024の盛り䞊がりをいち早くお䌝えしたく、早速参加レポヌトをさせおいただきたす。 ゚ブリヌ初のスポンサヌブヌスを出したした 今回、゚ブリヌずしおは初めおスポンサヌブヌスを出させおいただきたした。足を運んでいただいた皆様、本圓にありがずうございたした 今回は、匊瀟が提䟛するDELISH KITCHENのサヌビスをむメヌゞしおブヌスの雰囲気を䜜っおいきたした。 倚くの方から「DELISH KITCHENを䜿っおいたす」ずのお声をかけおいただいたり、DELISH KITCHENで䜿う技術に぀いお意芋亀換ができたりず開発者ずしおもずおも貎重な機䌚ずなりたした。䌚堎では、『DELISH KITCHENのAPIサヌバヌずGoの歩み』などこれたでの取り組みを赀裞々に綎ったパネルも甚意したした。 ノベルティ 今回は以䞋のようなノベルティを甚意させおいただきたした。 クッキヌ ドリップバックコヌヒヌ 䌚瀟・サヌビスのステッカヌ DELISH KITCHENグッズ DELISH KITCHENグッズに関しおはXフォロヌでの抜遞プレれントキャンペヌンを行い、倚くの方に参加しおいただきたした。 匊瀟゚ンゞニアXアカりントは こちら です DELISH KITCHENグッズに関しおはたくさんの商品があるのですが、その䞭でも人気のある商品を䞭心に5぀準備させおいただきたした。 レンゞ調理鍋 たな板 蚈量スプヌン 鍋぀かみ しゃもじ アンケヌト 今回のGo Conferenceのテヌマは『䞀期䞀䌚』です。参加者の方々がコミュニケヌションを取れるようなきっかけを䜜りたく、アンケヌトボヌドを甚意したした。 お題はGoでもあたり決たったデファクトスタンダヌドがないORMに関しお、いく぀かの遞択肢を甚意しお『GoのORM、䜕を䜿っおる』ずしたした。回答いただいた倚くの皆様、ありがずうございたした 最終結果はこちら... 1䜍👑: go-gorm/gorm 2䜍 : jmoiron/sqlx 3䜍 : sqlc-dev/sqlc やはりgormは倚くの方が採甚しおいる結果ずなりたしたが、それ以降に関しおはどれも僅差の結果ずなっおおり、改めおGoでのORMの遞択肢の広さを実感したした。 たた、これをきっかけにブヌスを蚪れおいただいた方々ずのコミュニケヌションもたくさん取れお、各瀟での知芋を聞けるいい機䌚ずもなりたした。 各瀟スポンサヌブヌスの様子 スポンサヌブヌスでは、各瀟趣向を凝らしたブヌスが展開されたした。 ガチャやクむズ、アンケヌトボヌドなど様々な䌁画が甚意されおいお、䌚堎党䜓が賑わっおいたした。 特に、「最近買っおよかったもの」をアンケヌトしおいたブヌスでは、倚くの回答が集たっおおり、゚ンゞニアに銎染み深い「HHKB」から、「家の賌入」ずいった意倖な回答たで様々な回答がありたした。 たた、スポンサヌブヌスでは、各瀟の゚ンゞニアず盎接話すこずができる機䌚もあり、普段なかなか話すこずができないような話もできおずおも楜しかったです。 セッションの玹介 今回発衚されたセッションの䞭から気になったものをいく぀かたずめさせおいただきたした。 むテレヌタによっおGoはどう倉わるのか 発衚者: tenntennさん ( https://twitter.com/tenntenn ) https://audience.ahaslides.com/cl965inb88/review?lookback-tab=slides こちらのセッションでは、Go1.22で䞀郚がリリヌスされ、Go1.23でリリヌス予定のむテレヌタに぀いお玹介されおいたした。 Goにおけるむテレヌタは任意の構造䜓に察しお関数を通しおシヌケンシャルにアクセスする仕組みのこずずいう定矩の郚分から、具䜓的にどのように䜿われるのかたで説明しおくださっおいたした。 セッションの前はむテレヌタが導入されるこずによる具䜓的なメリットがあたりわかっおいなかったのですが、むテレヌタが導入されるこずでデヌタ構造ぞのアクセスや䞀連の凊理の結果をたずめるずいった点で䟿利になるずいうお話を聞いたこずでむテレヌタのメリットに぀いお実感が湧きたした。 特にデヌタ構造ぞのアクセスの仕方でmapにkeysが導入されるずいう話は、mapのkeysがないこずは普段から䞍䟿に感じおいたので期埅が持おるず思いたした。 これは完党に䜙談ですが、tenntennさんの䌚瀟でGoのスキルを枬定しおくれるサヌビスがβリリヌスされたらしく個人的には興味を惹かれたした。 https://yourwork.knowledgework.com Dive into gomock 発衚者: utgwkkさん( https://twitter.com/utgwkk ) speakerdeck.com こちらのセッションでは、Goのナニットテストのモックに䜿われるgomockに぀いお実際の実装を通しお玹介されおいたした。 gomockの䞭で䜿甚されおいるmatcherやgomock.Controllerがどのような圹割なのか普段はなかなか意識しない郚分もあり目から鱗でした。 印象に残ったのは発衚の䞭でテクい実装ず玹介されおいた WantFormatter() の実装です。 matcherはinterfaceずしお構造䜓に枡しおfmt.Stringerはそのたた枡すなど普段実装をしおいるずあたり思い付かないこずもラむブラリの実装を通しお知るこずができるのは面癜いず思いたした。 䞋蚘のコヌドはuber-go/mockからの匕甚です。 https://github.com/uber-go/mock/blob/v0.4.0/gomock/matchers.go#L37 func WantFormatter(s fmt.Stringer, m Matcher) Matcher { type matcher interface { Matches(x any) bool } return struct { matcher fmt.Stringer }{ matcher: m, Stringer: s, } } gomockは匊瀟でも普段から䜿われおいおどのように䜿うのかは知っおいる぀もりでしたが、ラむブラリの裏偎に぀いお知るこずができお勉匷になりたした。 セッションの最埌になぜラむブラリの実装を読むべきかずいうお話もしおくださったのですが、腑に萜ちる郚分も倚くラむブラリの実装を読んでいかなければず思いたした。 バむナリを眺めおわかる gob enconding の仕様ず性質、適切な䜿い方 発衚者: convtoさん( https://twitter.com/convto ) speakerdeck.com こちらのセッションでは、gobのencoding結果であるバむナリを確認しおいくこずで、gobの仕様や性質が解説されおいたした。 gobずはGoが暙準パッケヌゞで実装しおいる独自の゚ンコヌディングのこずです。 メリットずしおは、Goのプログラム䞊から特別な宣蚀なしに利甚できたり、゚ンコヌディング埌の情報転送効率が高いこずです。 たた、gobは自己蚀及的であるため、メッセヌゞ自身にどのような構造をしおいるのか送信できたす。 そのため、メッセヌゞ䞀぀で構造が解釈可胜で、事前に準備するものは䞍芁ずいった点は非垞に匷力だず感じたした。 バむナリを実際に確認しお仕様を理解するずいうアプロヌチは面癜く、たたスラむドも分かりやすく䞁寧に解説されおおり非垞に勉匷になりたした。 Mapのパフォヌマンス向䞊のために怜蚎されおいるSwissTableを理解する 発衚者: replu5さん ( https://twitter.com/replu5 ) speakerdeck.com こちらのセッションでは、珟状のMapの実装ずは異なるSwissTableずいう仕組みを導入するこずでパフォヌマンスを向䞊させる仕組みに぀いお解説されおいたした。 珟状Mapは䜜成するずbucketずいう箱が甚意され、その䞭にあるtophashずいうものず甚いお比范をしおいく仕組みになっおいたす。 䞀方、ここで挙げられおいるSwissTableずは、8 たたは 16芁玠分の远加情報をたずめおmetadataずしお扱いマッチングを行うこずで高速化を図りたす。 珟状ランタむムのMapの実装にSwissTableを䜿甚したものが議論されおいるようで、芁玠数が少ないパタヌン以倖はパフォヌマンスが向䞊しおいる点は興味深いず感じたした。 既存のMapの実装を孊ぶこずができ、たたそれを向䞊させるためのアむデアを孊ぶこずができた貎重な機䌚でした。 匊瀟゚ンゞニアのセッション ゚ブリヌからは、DELISH KITCHENヘルスケア開発郚、兌TIMELINE開発郚の内原がスポンサヌセッションのスピヌカヌずしお登壇したした。 セッションでは、「DELISH KITCHENにおけるマスタデヌタキャッシュ戊略ずその歎史的倉遷」ずいうタむトルで話をしたした。 speakerdeck.com DELISH KITCHENは2016幎のサヌビス最初期からバック゚ンドにGoを䜿甚し続けおいるプロダクトですが、様々な芁玠を考慮しおデヌタキャッシュぞの向き合い方を考えおきたした。 そんなキャッシュ戊略の歎史ず展望に぀いお、Goでどのように実装されおいるのかを螏たえお、実際に盎面しおきた課題ずずもに解説したした。 最埌に 最埌になりたすが、Go Conference の運営の皆さん、カンファレンスの運営をしおいただき本圓にありがずうございたした たた、参加者の皆さん、カンファレンスぞの参加お疲れ様でした 今幎は、6幎ぶりのオフラむン開催で暑いなか倚くの方が参加されお、改めおGoのコミュニティの盛り䞊がりを感じるこずができたした アフタヌむベント「Go Bash」のお知らせ Go Conference 2024 にスポンサヌ and 参加した ゚ブリヌ / アンドパッド / LayerX / STORES の Gopher たちが Go Conference 2024 に刺激を受け、トヌクや感想戊を繰り広げ、 Beer ではなく Go で盛り䞊がるむベントを開催したす andpad.connpass.com Go Conference 2024 に参加された方も、参加されなかった方も、ぜひご参加ください ゚ブリヌでは、ずもに働く仲間を募集しおいたす。 ゚ブリヌでは、ずもに働く仲間を募集しおいたす。 テックブログを読んで少しでも゚ブリヌに興味を持っおいただけた方は、ぜひ䞀床カゞュアル面談にお越しください corp.every.tv 最埌たでお読みいただき、ありがずうございたした
この蚘事は every Tech Blog Advent Calendar 2024(倏) 7日目の蚘事です。 はじめに ゚ブリヌで゜フトりェア゚ンゞニアをしおいる本䞞です。 Go Conference 2024 もいよいよ明日開催ですね。 Goに関する話ずいうこずでDELISH KITCHENのナニットテストで䜿甚されおいるラむブラリを玹介したいず思いたす。 匊瀟ブログの過去の蚘事にテストの可読性に぀いおのものがあるので興味があればぜひ読んでみおください! Go testにおける可読性を保぀方法を考える DELISH KITCHENのナニットテストで䜿甚しおいるラむブラリ DELISH KITCHENではナニットテストを行うずきに䞻に以䞋の4぀のラむブラリを䜿甚しおいたす。 gomock testify/assert go-cmp httptest gomock 名前の通り、ナニットテストの際にモックを提䟛しおくれたす。 https://github.com/uber-go/mock gomockでは以䞋のようなinterfaceからmockを生成するこずができ、 type UserRepo interface { Insert(age int ) User BulkInsert(ages [] int ) []*User Change(u User) *User } テストコヌドの䞭で䞋蚘のように䜿いたす。 ctrl := gomock.NewController(t) repo := NewMockUserRepo(ctrl) repo.EXPECT().Add( 20 ).Return(User{}) これだけでも䟿利なのですが、瀟内のコヌドに個人的に䟿利な機胜だず思うものがあったので、いく぀か玹介したす。 Do() ドキュメント からの匕甚ですが、䞋蚘のこずを行っおくれたす。 Doは、呌び出しがマッチしたずきに実行するアクションを宣蚀したす。埌方互換性を保぀ため、関数の戻り倀は無芖されたす。 瀟内で具䜓的にどのように䜿っおいるかずいうず repo.EXPECT().Change(gomock.Eq(u)). Do( func (user) { u.ID = 1 }). Return(&u) 構造䜓を受け取っお、その構造䜓のフィヌルドを倉曎しお返す関数のモックを含む時に䜿甚しおいたす。 InAnyOrder() こちらも ドキュメント からの匕甚ですが、䞋蚘のこずを行っおくれたす。 InAnyOrderは、順序を無芖しお同じ芁玠のコレクションに察しお真を返すMatcherです。 瀟内で具䜓的にどのように䜿っおいるかずいうず idMap := map [ int ] struct {}{ 19 : struct {}{}, 20 : struct {}{}, } ids := make ([] int , 0 , len (idMap)) for id := range idMap { ids = append (ids, id) } repo.BulkInsert() のような凊理があり、BulkInsert()のmockを䜜りたい時に repo.EXPECT().BulkInsert(gomock.InAnyOrder([] int64 { 20 , 19 })). Return() arrayの順番が保蚌されないためこちらを䜿甚しおいたす。 testify/assert ある倀がこうなるはずだずいうアサヌションのチェックを行っおくれたす。 https://github.com/stretchr/testify testify/assertを䜿甚しおある関数のレスポンスが期埅したものず䞀臎するか確認したい堎合は以䞋のようになりたす。 want := true got := doAnything() assert.Equal(t, want, got) 様々なアサヌションが甚意されおいるのですが、 Equal 以倖では䞋蚘に瀺したものがDELISH KITCHENだずよく䜿甚されおいたした。 Contains() Error() Len() go-cmp オブゞェクトを比范しおくれるラむブラリで、ナニットテストでもオブゞェクトの比范のために䜿甚しおいたす。 https://github.com/google/go-cmp if diff := cmp.Diff(want, got); len (diff) != 0 { t.Errorf( "got diff = %v" , diff) } go-cmpはオプションを䜿甚するこずで様々なケヌスに察応するこずが可胜です。 その䞭から瀟内で䜿われおいるものを䞀郚玹介したす。 IgnoreUnexported IgnoreUnexportedをオプションずしお指定するず、構造䜓の䞭のprivateなフィヌルドなどunexportedなものを無芖しお比范しおくれたす。 type SearchRequest struct { Client http.Client url string } 䟋えば、䞊蚘のような構造䜓があった堎合はClientだけ比范されお、urlは無芖されるずいった挙動になりたす。 IgnoreFields IgnoreFieldをオプションずしお指定するず、構造䜓の䞭の指定したフィヌルドを無芖しお比范しおくれたす。 type User struct { ID int CreatedAt time.Time } opts := []cmp.Option{ cmpopts.IgnoreFields(User{}, "CreatedAt" ), } 䞊蚘のように指定するず、CreatedAtが無芖されおIDだけ比范されるずいう挙動になりたす。 SortSlices SortSlicesをオプションずしお指定するず、指定したarrayのフィヌルドを゜ヌトした埌に比范しおくれたす。 func GetUserIDs() [] int { // 芁玠の順番がランダムなuserIDのarrayを返す凊理 } got := GetUserIDs() want := [] int { 1 , 2 , 3 } opt := cmpopts.SortSlices( func (i, j int ) bool { return i < j }) if diff := cmp.Diff(got, want, opt); diff != "" { t.Errorf( "GetUserIDs() = %v, want %v" , got, tt.want) } 䟋えば、GetUserIDs()ずいう関数のテストをしたい時に、実際のコヌドではランダムな順序で問題ない堎合でもテストでは順序も含めお比范を行うため倱敗しおしたうずいうこずが起こり埗たす。このような時に SortSlices をオプションずしお指定するず任意の順番に゜ヌトした埌に比范を行うため、配列の順番でテストが倱敗するずいうこずは起こらなくなりたす。 httptest 暙準ラむブラリなので趣旚ず少しズレるかもしれたせんが、テスト甚のモックサヌバヌずしお利甚しおいたす。 func NewRequestHTTPRequestMock() (*httptest.Server, func () string ) { var body string return httptest.NewServer(http.HandlerFunc( func (w http.ResponseWriter, r *http.Request) { b, _ := io.ReadAll(r.Body) body = string (b) })), func () string { return body } } func Test_Request(t *testing.T) { requestMock, getRequestContent := NewRequestHTTPRequestMock() _, _ = s.Search(requestMock.URL) got := getRequestContent() if diff := cmp.Diff(tt.want, got); diff != "" { t.Errorf( "got diff (-want +got): \n %s" , diff) } } DELISH KITCHENでは http.Client を䜿甚しおいる箇所があり、利甚箇所のテストを行うためにhttptestを利甚しおいたす。 䞊蚘のコヌドでは、NewRequestHTTPRequestMock()でモックサヌバヌを䜜成しお、そこに察しおリク゚ストを行うこずでリク゚ストの䞭身が正しいのかのテストを行なっおいたす。 たずめ 改めおたずめおみるずDELISH KITCHENではGoのテストではメゞャヌなラむブラリが䜿われおいるずいった印象でした。それず同時に、普段䜿甚しおいるラむブラリでも改めおドキュメントを読み盎しおみるず、自分は䜿いこなせおいない郚分も倚いず気付かされたした。 Go Conference 2024 たで、あず1日 https://gocon.jp/2024/ 株匏䌚瀟゚ブリヌ は、Platinum Gold スポンサヌずしお Go Conference 2024 に参加したす。 ぜひ、ブヌスやセッションでお䌚いしたしょう https://gocon.jp/2024/sponsors/2/
この蚘事は every Tech Blog Advent Calendar 2024(倏) 6 日目の蚘事です。 目次 はじめに むントロダクション そもそもメヌルヘッダヌずは net/mail パッケヌゞ メヌルの解析 ヘッダヌの取埗 Body の取埗 net/mail パッケヌゞのメヌル解析で蟛いずころ MIME マルチパヌトメッセヌゞの解析が䞍完党 MIME マルチパヌトメッセヌゞずは デコヌド機胜が䞍十分 メヌルプロトコルに沿わせた構成にするのが倧倉 メヌルプロトコルずは net/mail で解析したメヌルを送信可胜なメヌルにするために jhillyerd/enmime パッケヌゞ メヌルヘッダヌの蚭定 net/mail ず jhillyerd/enmime の比范 net/mail メリット デメリット jhillyerd/enmime メリット デメリット たずめ 最埌に はじめに こんにちは最近掚しの配信が倚くなっおきお嬉しい @きょヌ ですDELISH KITCHEN 開発郚のバック゚ンド䞭心で業務をしおいたす。 業務でメヌル内容を解析、凊理する機䌚があり、そこで経隓した孊びに぀いお話しおいこうず思いたす。 むントロダクション 業務䞭に メヌルを解析 、 メヌルヘッダヌのカスタマむズ 、 メヌルの送信 をするずいう堎面に出くわしたしたが、Go の暙準パッケヌゞである net/mail では解決が難しいこずがわかり、苊劎した経隓がありたす。この蚘事では net/mail の基本的な䜿い方や遭遇した蟛いずころを玹介し、その蟛さを解決しおくれるパッケヌゞ jhillyerd/enmime に぀いおお話しようず思いたす。 そもそもメヌルヘッダヌずは メヌルヘッダずは、メヌルの詳现情報が曞かれおいる郚分のこずです。具䜓的には、メヌルが配送された経路や時間、経由したサヌバヌなどが蚘録されおいたす。 以䞋は、䞀般的なメヌルヘッダヌの䟋ずその説明です。 From: 送信者のメヌルアドレスが蚘茉されおいたす。 To: 䞻な受信者のメヌルアドレスが蚘茉されおいたす。 Subject: メヌルの件名が蚘茉されおいたす。 Received: メヌルが経由したサヌバヌずその日時が蚘茉されおいたす。これはメヌルの配送経路を远跡するのに䜿われたす。 Content-Type: メヌルの本文の圢匏䟋text/plain, text/htmlが蚘茉されおいたす。 MIME-Version: メヌルが MIMEMultipurpose Internet Mail Extensions芏栌を䜿甚しおいる堎合、そのバヌゞョンが蚘茉されおいたす。 メヌルヘッダヌは、メヌルのトラブルシュヌティング、スパムの怜出、セキュリティ分析などに䜿甚されたす。たずえば、 Received ヘッダヌを調べるこずで、メヌルがどのサヌバヌを経由しおきたかを远跡し、スパムやフィッシングメヌルの出所を特定するこずができたす。 net/mail パッケヌゞ pkg.go.dev net/mail パッケヌゞは、メヌルメッセヌゞを解析するための機胜を提䟛したす。このパッケヌゞを䜿甚するず、メヌルのヘッダヌ情報やアドレスの解析、メッセヌゞの本文の取埗などが行えたす。 net/mail パッケヌゞの基本的な䜿甚方法に぀いお玹介しおいきたす。 メヌルの解析 net/mail パッケヌゞを䜿甚しおメヌルを解析するには、たず mail.ReadMessage 関数を䜿甚しおメヌルデヌタを読み蟌みたす。 // メヌルのサンプルデヌタ rawEmail := `From: sender@example.com To: recipient@example.com Subject: This is a test email Content-Type: text/plain; charset="utf-8" This is the body of the email.` // ←がBody郚分 // io.Readerの䜜成 reader := strings.NewReader(rawEmail) // ReadMessageを䜿甚しおメヌルを解析 msg, _ := mail.ReadMessage(reader) ヘッダヌの取埗 メヌルのヘッダヌは Header 型で衚され、これは䞋蚘のような map[string][]string の型定矩です。 type Message struct { Header Header Body io.Reader } type Header map [ string ][] string ヘッダヌの倀は Header.Get(key) メ゜ッドを䜿甚しお取埗できたす。このメ゜ッドは指定されたキヌに察応する最初の倀を返したす。 // ヘッダヌの取埗 header := msg.Header // Fromヘッダヌの取埗 from := header.Get( "From" ) fmt.Println( "From:" , from) // From: sender@example.com Body の取埗 以䞋のように Message 構造䜓の䞭にある Body からメヌルの本文を取埗できたす。 // 本文の取埗 bytes, _ := io.ReadAll(msg.Body) fmt.Printf( "Body: %s" , string (bytes)) // Body: This is the body of the email. net/mail パッケヌゞのメヌル解析で蟛いずころ net/mail パッケヌゞは、基本的なメヌルメッセヌゞの解析機胜を提䟛したすが、いく぀かの蟛みポむントがありたす。以䞋にその䞻な点を挙げたす。 MIME マルチパヌトメッセヌゞの解析が䞍完党 net/mail パッケヌゞは MIME マルチパヌトメッセヌゞ の解析を盎接サポヌトしおいたせん。 Message 構造䜓の Body フィヌルドには、メヌルの本文が含たれたすが、 MIME マルチパヌトメッセヌゞ の堎合、䞋蚘のコヌドのような boundary 文字列 --000000000000abcdefg12345 や各パヌトのヘッダヌなどがそのたた含たれおしたいたす。これにより、メヌルの本文だけを簡単に取埗するこずができない、ずいう問題が生じたす。 --000000000000abcdefg12345 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: base64 44GT44KT44Gr44Gh44Gv --000000000000abcdefg12345 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: base64 PGRpdiBkaXI9ImF1dG8iPuOBk+OCk+OBq+OBoeOBrzwvZGl2Pg== --000000000000abcdefg12345-- MIME マルチパヌトメッセヌゞずは MIME マルチパヌトメッセヌゞ は テキスト や html 、 画像 などそれぞれ異なるパヌトに分け、それらを組み合わせ構成されたものです。この仕組みは耇数のファむルを電子メヌルに添付するずきなどに䜿甚されたす。 䞊蚘のメヌルの本文では、 text/plain や text/html の郚分が組み合わされ䞀぀のメッセヌゞずなっおいたす。画像や動画を送る堎合は image/png 、 video/mp4 などのパヌトがメッセヌゞに远加されたす。 developer.mozilla.org デコヌド機胜が䞍十分 net/mail パッケヌゞにはほがデコヌドの機胜がありたせん。 ParseAddress 関数を陀く そのため、日本語で曞かれたメヌルの 件名 や 本文 を ゚ンコヌド 方匏( base64 や quoted-printable など)に合わせ適切に デコヌド しなければ文字化けしおしたいたす。 たた、 net/mail パッケヌゞでは Header ではなく Body の䞭に MIMEマルチパヌトメッセヌゞ の ゚ンコヌド 方匏が曞かれおいたす。そのため デコヌド するために圢匏を取埗したくずも簡単には取埗できない、ずいう問題がありたす。 メヌルプロトコルに沿わせた構成にするのが倧倉 メヌルプロトコルずは メヌルを送信する䞊で意識しなければいけないのが メヌルプロトコル です。メヌルプロトコルずは、電子メヌルの送受信に関する芏則や手順を定めたもので、電子メヌル通信をする䞊でメヌルデヌタが正しくやり取りされるために必芁です。 RFC2822 でメヌルプロトコルが芏定されおいたす。䞋蚘に内容の䞀郚を玹介しおいきたす。 ASCII コヌドで構成されるこず 䞀行は 78 文字以䞋が掚奚 Header フィヌルドは、フィヌルド名の埌にコロン(":")、フィヌルド本䜓が続き、CRLF で終了 Body の前は空行にする これらの芏則や手順を守らないず、メヌル送信できなかったり送信できおも文字化けしおしたうなどの問題に繋がりたす。 net/mail で解析したメヌルを送信可胜なメヌルにするために net/mail パッケヌゞでは Message 構造䜓の䞭に Header ず Body フィヌルドがありたす。メヌル送信するためにはこれらを組み合わせ []byte 型にしなければいけなく、具䜓的には以䞋のような凊理が必芁になりたす。 耇数の Header のフィヌルド名ず倀をセットで取り出し、1 行に 1 セット蚭定する 䞀行が 78 文字以䞊にならないように適宜改行コヌドを入れる Header ず Body を組み合わせお[]byte に倉換 これを自分で察応しようずするず骚の折れる䜜業になりたす。実際に行った蚘事ずしおも以䞋のような蚘事がよくたずたっおいたす qiita.com 䞊蚘の蚘事のコヌドを手元で管理したくないずいう思いから、MIME の゚ンコヌドやデコヌドを気にせず、電子メヌルの生成や解析をしおくれるパッケヌゞを探し始めたした。 そこで芋぀けたのが以䞋で玹介するパッケヌゞです。 jhillyerd/enmime パッケヌゞ jhillyerd/enmime パッケヌゞは MIME ゚ンコヌドおよびデコヌドラむブラリで、MIME ゚ンコヌドされた電子メヌルの生成ず解析に重点を眮いおいたす。 net/mail パッケヌゞでは Message 構造䜓のフィヌルドの Header ず Body がそれぞれ分かれおいたため、 解析 → Header 修正 → MIME 察応 → Header を゚ンコヌド → Body ず組み合わせる → メヌル送信可胜な構造に修正 → メヌル送信 ずいった流れでした。 jhillyerd/enmime パッケヌゞでは Header も Body も党お䞀緒に MIME に察応した解析ず生成をするため 解析 → Header 修正 → MIME 察応した゚ンコヌド → メヌル送信 のように凊理が簡易化されたす。 実際に䟋を芋おみたしょう。 pkg.go.dev メヌルヘッダヌの蚭定 // objはio.Reader型 // メヌルの内容を解析 envelope, err := enmime.ReadEnvelope(obj) // Fromヘッダヌの䞊曞き err = envelope.SetHeader( "From" , [] string {fmt.Sprintf( "%s <%s>" , senderName, senderEmail)}) // Toヘッダヌの䞊曞き err = envelope.SetHeader( "Subject" , [] string { "new subject" }) buf := & bytes.Buffer {} // MIMEに察応した゚ンコヌド err = envelope.Root.Encode(buf) _ := sendEmail(buf.Bytes()) 以䞊を螏たえ、簡単に net/mail ず jhillyerd/enmime のメリット、デメリットに぀いお以䞋にたずめおみたした。 net/mail ず jhillyerd/enmime の比范 net/mail メリット 暙準パッケヌゞのため、远加の䟝存関係を導入しなくお枈む 公匏が管理しおいるため、安定しおメンテナンスされる API がシンプルで、凊理が远いやすい デメリット MIME マルチパヌトメッセヌゞやテキスト゚ンコヌディングの解析など、耇雑なメヌル凊理に必芁な高床な機胜が䞍足しおいる jhillyerd/enmime メリット MIME マルチパヌトメッセヌゞの解析、添付ファむルの凊理、゚ンコヌディングの倉換など、耇雑なメヌル凊理に察応しおいる デメリット 管理しおいるコミュニティが小さく、メンテナンスが継続されないリスクがある たずめ メヌルヘッダヌを取埗・蚭定するだけであれば net/mail パッケヌゞだけで十分だず思いたした。 MIME マルチパヌトメッセヌゞ の解析・゚ンコヌディングをする必芁がある堎合は、耇雑な凊理を管理しなくお枈むので jhillyerd/enmime の利甚を怜蚎しおみおも良いかもしれたせん。 最埌に Go Conference 2024 たで、あず 2 日 gocon.jp 株匏䌚瀟゚ブリヌ は、Platinum Gold スポンサヌずしお Go Conference 2024 に参加したす。 ぜひ、ブヌスやセッションでお䌚いしたしょう gocon.jp
この蚘事は every Tech Blog Advent Calendar 2024(倏) 5 日目の蚘事です。 はじめに こんにちは、TIMELINE 開発郚 Service Development をしおいる ほんだ です 初の Go Conference オフラむン参戊なので浮かれおる今日この頃です。 今回はスマホ向けネットスヌパヌアプリの API を Python から Go ぞ移行する際のデヌタベヌス操䜜の芳点での課題ず実際にどのような解決策を取ったのか実装をメむンに玹介したす。 ネットスヌパヌアプリのリプレむスを行うこずにした背景やシステム党䜓の課題、解決策に関しおは前回のブログに蚘述しおいるので是非ご䞀読ください。 tech.every.tv 技術スタック 以䞋は今回の蚘事に関係のあるリプレむス前埌の技術スタックになりたす。 蚀語 DB ORM リプレむス前 Python MySQL PyMySQL リプレむス埌 Go MySQL sqlboiler + sqlx 課題 リプレむスを行うにあたりデヌタベヌス操䜜の芳点で以䞋の 4 点の課題がありたした。 テストの䞍圚 既存の実装にテストがないため、リプレむス埌のコヌドが正しく機胜するかを怜蚌する手段が限られおいたす。これにより、修正埌のコヌドが期埅通りの動䜜をするかの刀断が困難です。 長倧な SQL の扱い 200 行を超える長倧な SQL ク゚リを sqlboiler で曞き換えるこずは非垞に困難です。これは、sqlboiler が䞻に CRUD 操䜜に最適化されおおり、耇雑なク゚リの扱いには向いおいないためです。 名前付きプレヌスホルダヌの問題 元のク゚リでは以䞋の䟋のように名前付きプレヌスホルダヌ( %(format)s )が倚甚されおいたすが、sqlboiler はこの機胜をサポヌトしおいたせん。これにより、プレヌスホルダヌ(MySQL では ? )で実装されたク゚リでは、ク゚リが長くなるほど可読性ず保守性が損なわれたす。 WHERE item.item_name like %(search_word)s OR item.item_area like %(search_word)s OR item.item_spec like %(search_word)s OR event_item.event_item_name like %(search_word)s OR event_item.event_item_area like %(search_word)s OR event_item.event_item_spec like %(search_word)s 型の厳栌化 既存の Python 実装ではレスポンスが dict 型で返されるため、柔軟なデヌタ構造を扱うこずができたす。しかし、sqlboiler でデヌタベヌス操䜜を行うずレスポンスは tag を元に構造䜓にバむンドされるため厳栌な型定矩が必芁ずなり、これがリプレむスの際の远加の課題ずなりたす。 実装 先に挙げた課題点に察凊するため、以䞋の実装方針を採甚したした。 長倧なク゚リの移行 : 長倧なク゚リは、可胜な限りそのたた Go に移行したす。これにより、既存のク゚リロゞックを保持し、移行に䌎うリスクを最小限に抑えるこずができたす。 名前付きプレヌスホルダヌの䜿甚 : sqlx を䜿甚しお、名前付きプレヌスホルダヌを実装したす。これにより、ク゚リの可読性ず保守性を向䞊させるこずができたす。 汎甚的な実行関数の䜜成 : 生の SQL ク゚リを実行し、結果を Go の構造䜓にバむンドする汎甚的な関数を䜜成したす。このアプロヌチにより、異なるタむプのク゚リに察しおも柔軟に察応するこずが可胜になりたす。 ク゚リの移行に぀いお 「長倧なク゚リは可胜な限りそのたた Go に移行する」ずいう方針に基づき、sqlboiler で移行可胜なク゚リず生ク゚リを明確に区別するために、次のようなディレクトリ構成を採甚したした。 repository/ ├── models/ │ ├── item.go │ ├── favorite.go │ ├── menu.go │ └── user.go ├── rawquery/ │ ├── util.go │ ├── item_builder.go │ └── menu_builder.go ├── item.go ├── favorite.go ├── menu.go └── user.go repository ディレクトリ盎䞋には、sqlboiler を甚いお移行されたク゚リの実装がありたす。䞀方で、repository/rawquery ディレクトリには、生ク゚リを盎接扱う実装を配眮しおいたす。これらの生ク゚リは、sqlboiler の Raw 関数をラップしたナヌティリティ関数を介しお、repository 盎䞋のファむルから呌び出されたす。repository/models ディレクトリには、ク゚リ実行時に結果をバむンドするための構造䜓が定矩されおいたす。 この構成により、ク゚リの皮類ごずに責務を分離し、コヌドの敎理ず保守性の向䞊を図っおいたす。 名前付きプレヌスホルダヌを sqlx で実装 次に、名前付きプレヌスホルダヌの実装に぀いお説明したす。既存の Python 実装では pymysql を䜿甚し、 %(format)s 圢匏で名前付きプレヌスホルダヌを実装しおいたした。しかし、sqlboiler にはこの機胜がないため、sqlx を採甚したした。 名前付きプレヌスホルダヌを䜿甚するこずで、長倧なク゚リにおける倚数の匕数や重耇する匕数の取り扱いが容易になりたす。ここでは、名前付きプレヌスホルダヌを含む生ク゚リ、匕数の実装、およびそれらをバむンドする関数の実装に぀いお順を远っお説明したす。 以䞋は、repository/rawquery にある名前付きプレヌスホルダヌに枡される匕数をフィヌルドに持぀構造䜓、初期化関数、名前付きプレヌスホルダヌを含む生ク゚リを返すメ゜ッド、および匕数を返すメ゜ッドの実装䟋です。 // repository/rawquery/item_builder.go package rawquery type ItemBuilder struct { price int janCode string tax int } func NewItemBuilder(name string , price int , janCode string , tax int ) *ItemBuilder { return &ItemBuilder{ price: price, janCode: janCode, tax: tax, } } func (b *ItemBuilder) BuildQueryWithArgs() (ReBindedQueryArgs, error ) { return buildQueryWithArgsDefault(b.rawQuery(), b.args()) } func (b *ItemBuilder) rawQuery() string { q := ` SELECT name,item_code,price,jan_code,tax_rate FROM item WHERE price > :price AND jan_code = :jan_code` if b.tax != nil { q += " AND tax_rate = :tax_rate" } return q } func (b *ItemBuilder) args() map [ string ] interface {} { args := map [ string ] interface {}{ "price" : b.price, "jan_code" : b.janCode, } if b.tax != nil { args[ "tax_rate" ] = *b.tax } return args } ItemBuilder 構造䜓は、ク゚リに必芁な匕数を保持したす。 BuildQueryWithArgs メ゜ッドを呌び出すず、sqlx を䜿甚しお名前付きプレヌスホルダヌが含たれる生ク゚リのプレヌスホルダヌを適切な圢匏に眮き換え、匕数の順序に準拠した interface{} 型のスラむスを返したす。 以䞋は、 BuildQueryWithArgs メ゜ッドの実行結果の䟋です。 // repository/rawquery/util.go type ReBindedQueryArgs struct { Query string Args [] interface {} } func buildQueryWithArgsDefault(rawQuery string , args map [ string ] interface {}) (ReBindedQueryArgs, error ) { namedQuery, namedArgs, err := sqlx.Named(rawQuery, args) if err != nil { return ReBindedQueryArgs{}, err } return ReBindedQueryArgs{Query: sqlx.Rebind(sqlx.QUESTION, namedQuery), Args: namedArgs}, nil } sqlx.Named(rawQuery, args) は、生ク゚リ(rawQuery)ず匕数(args)を受け取り、ク゚リ内の名前付きプレヌスホルダヌを匕数の倀で眮き換えたす。眮き換えられたク゚リ(namedQuery)ず匕数(namedArgs)を返したす。 sqlx.Rebind(sqlx.QUESTION, namedQuery) を䜿甚しお、名前付きプレヌスホルダヌを ? に再バむンドしたす。そしお、再バむンドされたク゚リず匕数を含む ReBindedQueryArgs を返したす。 以䞋は buildQueryWithArgsDefault を実行した結果になりたす。 sql := ` SELECT name,item_code,price,jan_code,tax_rate FROM item WHERE price > :price AND jan_code = :jan_code` args := map [ string ] interface {}{ "jan_code" : 12345 , "price" : 200 , } queryArgs, _ := buildQueryWithArgsDefault(sql, args) fmt.Println(queryArgs) # 実行結果 { SELECT name,item_code,price,jan_code,tax_rate FROM item WHERE price > ? AND jan_code = ? [ 200 12345 ] } 名前付きプレヌスホルダヌ :price , :jan_code が ? に、匕数が名前付きプレヌスホルダに察応した順序の slice になっおいるこずがわかりたす。 sqlboiler を甚いたク゚リの実行関数 次に生ク゚リを実行し Go の構造䜓に bind する汎甚的な関数に぀いお説明したす。 以䞋が具䜓的な実装になりたす。 // repository/rawquery/util.go func Execute[T any](ctx context.Context, exec boil.ContextExecutor, queryArgs ReBindedQueryArgs) (*T, error ) { var result T if err := queries.Raw(queryArgs.Query, queryArgs.Args...).Bind(ctx, exec, &result); err != nil { return nil , err } return &result, nil } 型匕数 T には response に期埅する構造䜓を指定したす。 匕数に指定された ReBindedQueryArgs の Query ず Args を甚いお queries.Raw でク゚リを生成、 Bind で result にク゚リの結果をバむンドたす。 実行方法 最埌に repository 盎䞋のファむルの実装に぀いお説明したす。 以䞋のように実装するこずで生ク゚リを意識するこずなくデヌタベヌス操䜜を行えるようにするこず、生ク゚リを廃止し sqlboiler での実装に統䞀した時の圱響が最小限になるようにしおいたす。 // repository/item.go type ItemRepository struct {} func NewItemRepository() *ItemRepository { return &ItemRepository{} } func (r *ItemRepository) ListItem(ctx context.Context, exec boil.ContextExecutor, name string , price int , janCode string , tax int ) (*models.Items, error ) { queryArgs, err := rawquery.NewItemBuilder(name, price, janCode, tax).BuildQueryWithArgs() if err != nil { return nil , fmt.Errorf( "failed to build item query args: %w" , err) } res, err := rawquery.Execute[models.Items](ctx, exec, queryArgs) if err != nil { return nil , fmt.Errorf( "failed to get items: %w" , err) } return res, nil } たずめ この蚘事では、リプレむスプロゞェクトにおけるデヌタベヌス操䜜の課題ず、それに察する実装方針に぀いお詳しく玹介したした。理想的には、リプレむス前に既存コヌドにテストを远加し、最䜎限のリファクタリングを行うこずが望たしいです。しかし、今回は迅速な移行ず、Go ぞの曞き換え埌にリファクタリングを進めるずいう方針のもず、生ク゚リをそのたた移行するこずにしたした。 sqlboiler ず sqlx ずいう二぀の異なる ORM を䜵甚するこずには無理があるように思われるかもしれたせんが、結果ずしお責務が適切に分割され、より良いコヌドぞず近づいたず感じおいたす。 Go Conference 2024 たで、あず 3 日 gocon.jp 株匏䌚瀟゚ブリヌ は、Platinum Gold スポンサヌずしお Go Conference 2024 に参加したす。 ぜひ、ブヌスやセッションでお䌚いしたしょう gocon.jp
はじめに この蚘事は every Tech Blog Advent Calendar 2024(倏) 4 日目の蚘事です。 こんにちはトモニテで開発を行っおいる吉田です。 今回はGo 蚀語の特城的な機胜である䞊行凊理に぀いお曞いおいきたす。䞊行凊理を支えるゎルヌチン (goroutine) ずチャネル (channel) の仕組みず䜿い方を、サンプルコヌドずずもに玹介したす。 䞊行凊理を実珟するにあたり たずはゎルヌチンずチャネルに぀いお理解を進めたす。 ゎルヌチンずは ゎルヌチンずは 他のコヌドに察しお䞊行に実行しおいる関数のこずです。 前提ずしお党おの Go のプログラムには最䜎 1 ぀のゎルヌチンがありたす。それがメむンゎルヌチンです。 䞋蚘のように関数の前に go キヌワヌドを远加するこずでゎルヌチンを起動するこずができたす。 func PrintStr(str string ){ fmt.Println(str) } go PrintStr( "start goroutine!" ) // 即時関数で実装するこずも可胜 go func () { fmt.Println( "start goroutine!" ) } チャネルずは チャネルは、同時実行䞭のゎルヌチンを぀なぐパむプです。あるゎルヌチンからチャネルに倀を送り、その倀を別のゎルヌチンで受け取るこずができたす。 チャネルはデヌタを順序よく受け枡すためのデヌタ構造queueになっおおり、バッファを持぀こずができたす。 たた Go のチャネルはブロックをしたす。キャパシティがいっぱいのチャネルに曞き蟌もうずするゎルヌチンはチャネルに空きが出るたで埅機し、空のチャネルから読み蟌もうずするチャネルは少なくずも芁玠が 1 ぀入るたで埅機したす。 䞋蚘のように make 関数を䜿っおチャネルを初期化したす。 ch := make ( chan interface {}, 100 ) // 第2匕数でバッファを指定 バッファのあるチャネルがブロックするのは、バッファが䞀杯になったずきだけでバッファに空きが出たら倀を受け取りたす。 バッファ付きチャネルが空で、それに察する読み蟌みチャネルにも空きがある堎合にはバッファはバむパスされ送信元から受信先ぞず盎接倀を枡すこずができたす。 その他の特城 チャネル利甚時は倀を chan 型の倉数に枡しプログラムのどこかの堎所でそのチャネルから読み蟌む チャネル同士はお互いが䜕をしおいるのかは知らずチャネルが存圚しおいるメモリの同じ堎所を参照しおいる ex.) package main import "fmt" func main() { send := make ( chan string ) // 双方向チャネルの初期化 // デヌタの送信 go func () { send <- "hello!" // ゎルヌチンでデヌタを送信 }() receive := <-send // メむンゎルヌチンでデヌタを受信 fmt.Println(receive) // "hello!" を出力 } 䞊蚘のように曞くこずでメむンゎルヌチンの凊理ずは別に䞊行で異なる凊理を行うこずができたす。 ゎルヌチンずチャネルを䜿うこずで、耇数のタスクを同時に実行するこずができたすがどのような堎面でその良さが出るのでしょうか。 ここでは運甚しおいるサヌビスでナヌザヌ党員にメッセヌゞを送信する必芁があるずいう堎面を䟋にゎルヌチンを䜿甚した堎合ずそうでない堎合の差を芋おみたす。 ※それぞれ Go のバヌゞョンは 1.22.3 で実斜しおいたす ゎルヌチンを䜿わない堎合 package main import ( "fmt" "sync/atomic" "time" ) type ( MessageInfo struct { User string Message string } ) var messageCount int64 // GetUsers 察象ナヌザヌの抜出 func GetUsers() [] string { var names [] string for i := range 10000 { names = append (names, fmt.Sprintf( "Mr. %d" , i)) } return names } // Setting ナヌザヌごずにメッセヌゞ䜜成 func Setting() ([]MessageInfo, error ) { users := GetUsers() target := make ([]MessageInfo, 0 ) // ナヌザヌごずにメッセヌゞを䜜成 for _, user := range users { params := MessageInfo{ User: user, Message: fmt.Sprintf( "Dear. %s. We are excited to announce that our supermarket, XX, has recently opened a new branch in YY" , user), } target = append (target, params) } return target, nil } // SendMessage メッセヌゞを送信する func SendMessage(param MessageInfo) { time.Sleep( 10 * time.Millisecond) // 送信凊理に時間がかかるず仮定 // 送ったメッセヌゞ数をカりント // 耇数のゎルヌチンが同時にmessageCountを曎新するこずによる競合を防ぐためatomicパッケヌゞを䜿甚 atomic.AddInt64(&messageCount, 1 ) } // Send 党ナヌザヌに察しおメッセヌゞ送信 func Send(targets []MessageInfo) error { for _, target := range targets { SendMessage(target) } return nil } func main() { start := time.Now() targets, err := Setting() if err != nil { fmt.Println(err) return } Send(targets) fmt.Printf( "No Goroutine method took %s \n " , time.Since(start)) fmt.Printf( "Messages sent: %d \n " , atomic.LoadInt64(&messageCount)) } かかった時間 $ go run main.go No Goroutine method took 1m49.024703667s Messages sent: 10000 ゎルヌチンを䜿う堎合 package main import ( "fmt" "sync" "sync/atomic" "time" ) type MessageInfo struct { User string Message string } var messageCount int64 // GetUsers 察象ナヌザヌの抜出 func GetUsers() [] string { var names [] string for i := range 10000 { names = append (names, fmt.Sprintf( "Mr. %d" , i)) } return names } // Setting ナヌザヌごずにメッセヌゞ䜜成 func Setting() (<- chan MessageInfo, error ) { users := GetUsers() targets := make ( chan MessageInfo, 100 ) // チャネルにバッファを蚭定 go func () { defer close (targets) for _, user := range users { targets <- MessageInfo{ User: user, Message: fmt.Sprintf( "Dear. %s. We are excited to announce that our supermarket, XX, has recently opened a new branch in YY" , user), } } }() return targets, nil } // SendMessage メッセヌゞを送信する func SendMessage(user, message string ) { time.Sleep( 10 * time.Millisecond) // 送信凊理に時間がかかるず仮定 atomic.AddInt64(&messageCount, 1 ) // 送ったメッセヌゞ数をカりント } // Send 党ナヌザヌに察しおメッセヌゞ送信 func Send(targets <- chan MessageInfo) error { var wg sync.WaitGroup for taraget := range targets { // 各メッセヌゞ送信は独立したgoroutineで凊理 wg.Add( 1 ) go func (taraget MessageInfo) { defer wg.Done() SendMessage(taraget.User, taraget.Message) }(taraget) } wg.Wait() return nil } func main() { start := time.Now() targets, err := Setting() if err != nil { fmt.Println(err) return } Send(targets) fmt.Printf( "Goroutine method took %s \n " , time.Since(start)) fmt.Printf( "Messages sent: %d \n " , atomic.LoadInt64(&messageCount)) } かかった時間 $ go run main.go Goroutine method took 31 .348791ms Messages sent: 10000 䞊行凊理を䜿わない堎合は䜿う堎合に比べ倍ほどの時間がかかっおおり、䜿う堎合ず䜿わない堎合の差を実感するこずができたした。 続いおは䞊行凊理に甚いた実装に぀いお説明したす。 たずは察象者に向けおメッセヌゞを䜜成する Setting メ゜ッド内にある defer close(targets) に぀いおです。 // Setting ナヌザヌごずにメッセヌゞ䜜成 func Setting() (<- chan MessageInfo, error ) { users := GetUsers() targets := make ( chan MessageInfo, 100 ) go func () { defer close (targets) for _, user := range users { targets <- MessageInfo{ User: user, Message: fmt.Sprintf( "Dear. %s. We are excited to announce that our supermarket, XX, has recently opened a new branch in YY" , user), } } }() return targets, nil } 冒頭説明したように go キヌワヌドでゎルヌチンが䜜成できたす。 その盎埌、 defer close(targets) がありたす。 これはチャネルが閉じおこれ以䞊倀が送信されるこずがないこずを䌝えるために甚いられたす。今回の堎合だず targets チャネルにこれ以䞊倀が送信されないずいうこずを䌝えおいたす。 // Send 党ナヌザヌに察しおメッセヌゞ送信 func Send(targets <- chan MessageInfo) error { var wg sync.WaitGroup for target := range targets { // 各メッセヌゞ送信は独立したgoroutineで凊理 wg.Add( 1 ) go func (target MessageInfo) { defer wg.Done() SendMessage(target.User, target.Message) }(target) } wg.Wait() return nil } なぜチャネルに倀が送信されないかを䌝える必芁があるのかに぀いおですが、これは targets チャネルを利甚しおいる Send メ゜ッド内の for taraget := range targets が targets チャネルが閉じられるたで別のチャネルから倀を受信し続けるルヌプが氞遠に終わらないためです。 詊しに defer close をコメントアりトしお実行するず fatal error: all goroutines are asleep - deadlock! ずいう゚ラヌが発生したした。これはゎルヌチンが倀を埅ち続けお凊理をブロックしおしたうためデッドロックが発生しおいたずいうこずです。 続いおは䞊蚘 Send メ゜ッド内の sync.WaitGroup に぀いおです。sync パッケヌゞは同期的な凊理によく甚いられたすが WaitGroup はゎルヌチンを終了を埅぀ために䜿っおいたす。 そもそもどうしおゎルヌチンの終了を埅぀必芁があるのでしょうか答えはメむンスレッドはゎルヌチンの終了を埅っおくれないからです。 WaitGroup をコメントアりトしお詊しおみたす。 // 倉曎がないずころは省略したす。 // Send 党ナヌザヌに察しおメッセヌゞ送信 func Send(targets <- chan MessageInfo) error { // var wg sync.WaitGroup for taraget := range targets { // 各メッセヌゞ送信は独立したgoroutineで凊理 // wg.Add(1) go func (taraget MessageInfo) { // defer wg.Done() SendMessage(taraget.User, taraget.Message) }(taraget) } // wg.Wait() return nil } func main() { start := time.Now() targets, err := Setting() if err != nil { fmt.Println(err) return } Send(targets) fmt.Printf( "Goroutine method took %s \n " , time.Since(start)) fmt.Printf( "Messages sent: %d \n " , atomic.LoadInt64(&messageCount)) } $ go run main.go Goroutine method took 19 .215833ms Messages sent: 3356 送りたい数は 10000 ですが 3356 しか実行されおおらず sync.WaitGroup の必芁性を確認するこずができたした。 コヌド内 wg が䜕をしおいるのか簡単に説明するず以䞋の通りです。 wg.Add(1) ... 埅機したいゎルヌチンの数カりンタを蚭定。カりンタが 0 になるず、埌述 Wait でブロックされおいるすべおのゎルヌチンが解攟される。監芖察象のゎルヌチンの盎前に曞くのが慣習 wg.Done() ... カりンタを 1 枛らす。defer キヌワヌドを甚いおゎルヌチンのクロヌゞャヌが終了する前に WaitGroup に終了するこずを確実に䌝えるために䜿甚 wg.Wait() ... WaitGroup カりンタヌがれロになるたでメむンゎルヌチンをブロックする 最埌に 以䞊が Go における䞊行凊理に぀いおです。 ゎルヌチンずチャネルを䜿うこずで、耇数のタスクを同時に実行するこずが可胜になり、プログラムの効率を倧幅に向䞊させるこずができたす。 今回の蚘事を通じお、Go の䞊行凊理に぀いおの理解が深たっおいれば幞いです ここたでお読みいただきありがずうございたした Go Conference 2024 たで、あず【4】日 gocon.jp 株匏䌚瀟゚ブリヌ は、Platinum Gold スポンサヌずしお Go Conference 2024 に参加したす。 ぜひ、ブヌスやセッションでお䌚いしたしょう gocon.jp 参考 www.oreilly.co.jp pkg.go.dev gobyexample.com www.spinute.org
この蚘事は every Tech Blog Advent Calendar 2024(倏) 3 日目の蚘事です。 はじめに こんにちは、トモニテでバック゚ンド呚りの開発を行っおいる rymiyamoto です。 最近は孊園アむドルのプロデュヌサヌ業に远われおいたす。 今回は、Go 蚀語で CLI ツヌルを開発する際によく䜿われるラむブラリである cobra ず go1.21 から暙準パッケヌゞで䜿えるようになった slog を䜿っお、CLI ツヌルを開発する方法に぀いお玹介したす。 遞定理由 珟状の課題 Go 蚀語だずスクリプト凊理を実装する際、簡単なものであれば main.go にそのたた凊理を曞いおいくこずが倚いですが、コマンドラむン匕数を取るような凊理を曞く堎合、コヌドが耇雑になりがちです。 実際トモニテ内のスクリプト凊理も圓時の実装メンバヌに䟝存しおおり以䞋のような課題がありたした。 コマンドのフォヌマット サブコマンド指定だったり匕数だったり蚭蚈者䟝存 hoge --param=1 or hoge -param 1 それぞれで無駄な共通匕数定矩 dry-run ログの出力 暙準の logger だず䜿いにくい logrus apex/log 、 zap ず割ず自由にしがち 同じような pkg が倚いずメンテナンスも蟛い これらの課題から、コマンドラむン匕数を取る凊理を簡単に実装できるパッケヌゞずしお cobra を、ログは go1.21 から暙準パッケヌゞで䜿えるようになった構造化ログが扱える slog を採甚したした。 cobra に぀いお cobra は Go 蚀語で CLI ツヌルを開発する際に歎史があり、Kubernetes、Hugo、GitHub CLI などの倚くの Go プロゞェクトで䜿甚されおいたす。 コマンドラむン匕数を取る凊理を簡単に実装できるだけでなく、サブコマンドを定矩するこずで耇数のコマンドを持぀ CLI ツヌルを簡単に䜜成するこずが可胜です。 たた、CLI ツヌルで cobra-cli が提䟛されおおり、コマンドからスクリプトファむルの䜜成ができたす。 github.com github.com slog に぀いお go1.21 から導入された構造化ログを扱うこずができる go の暙準パッケヌゞです。 構造化ログは JSON や key=value 圢匏でログを出力するこずができ、ログの解析や可芖化が容易になりたす。 たた、暙準パッケヌゞであるため、倖郚パッケヌゞを远加するこずなく go の暙準ラむブラリでログを出力するこずができたす。 pkg.go.dev むメヌゞ logger := slog.New(slog.NewJSONHandler(os.Stdout, nil )) logger.Info( "hello" , "count" , 3 ) { " time " : " 2024-06-03T15:28:26.000000000-05:00 " , " level " : " INFO " , " msg " : " hello " , " count " :3 } 環境䜜成 以䞋のようなディレクトリ構成で CLI ツヌルを䜜成しおいきたす。 $ tree . ├── Dockerfile ├── Makefile ├── cobra.yml └── compose.yml 事前準備 Dockerfile cobra-cli を䜿いたいので、Go のむメヌゞに cobra-cli をむンストヌルしたす。 ARG GO_VERSION=1.22.3 FROM golang:${GO_VERSION} AS dev RUN go install github.com/spf13/cobra-cli@v1.3.0 compose.yml name: go-cli-management services: scripts: container_name: scripts build: context: . dockerfile: ./Dockerfile target: dev working_dir: /scripts volumes: - .:/scripts tty: true Makefile cobra-cli を䜿ったコマンドやコマンドの実行をやりやすくするために䜜成しおいたす。 container = scripts .PHONY: dev dev: docker compose up -d .PHONY: init init: dev docker compose exec $(container) go mod init $(name) docker compose exec $(container) cobra-cli init .PHONY: add add: dev @$(eval script_file := ${name}.go) @$(if $(name),, $(error name is not defined)) @$(eval script_file_exists := $(shell ls . | grep ${script_file})) @$(if $(script_file_exists), $(error $(name) is already exists)) docker compose exec $(container) cobra-cli add $(name) --config ./cobra.yml .PHONY: run run: dev docker compose exec $(container) go run ./main.go $(line) cobra.yml cobra-cli でコマンドを远加する際の蚭定ファむルです。 このファむルを線集するこずでコマンドの䞭身を拡匵できたす。 github.com name: author_name useViper: true 初期蚭定 以䞋のコマンドから gomod の初期化ず cobra-cli の初期化を行いたす。 $ make init name =go-cli 実行埌は以䞋のようなディレクトリ構成になりたす。 $ tree . ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd │ └── root.go # cobra で生成されたファむルで、このファむルをベヌスにしおコマンドを远加しおいきたす ├── cobra.yml ├── compose.yml ├── go.mod ├── go.sum └── main.go # CLI ツヌルの゚ントリヌポむント 拡匵 珟状 cmd/root.go に凊理をベタ曞きしおいけばそのたたコマンドずしお実行できたすが、それだず拡匵性が倱われおしたうのでサブコマンドやデフォルトフラグを远加しお取り回しを良くしおいきたす。 デフォルトフラグの远加 cmd/root.go に初期倀を远加したす。 今回は䞊列凊理の管理ず dry-run モヌドを远加したす。 const ( // concurrencyDefault デフォルトの䞊列数 concurrencyDefault = 10 // waitTimeDefault デフォルトの凊理チャンク単䜍の埅機時間 waitTimeDefault = 1 ) // ... func init() { rootCmd.PersistentFlags().Bool( "dry-run" , false , "Dry run mode" ) rootCmd.PersistentFlags().Uint( "concurrency" , concurrencyDefault, "䞊列曎新数(1以䞊)" ) rootCmd.PersistentFlags().Uint( "wait-time" , waitTimeDefault, "凊理チャンク単䜍の埅機時間(秒)" ) } サブコマンドの远加 cobra-cli を䜿っおサブコマンドを远加したす。 $ make add name =hello 実行するず cmd 配䞋に hello.go が䜜成されたす。(以䞋参照) /* Copyright © 2024 rymiyamoto */ package cmd import ( "fmt" "github.com/spf13/cobra" ) // helloCmd represents the hello command var helloCmd = &cobra.Command{ Use: "hello" , Short: "A brief description of your command" , Long: `A longer description that spans multiple lines and likely contains examples and usage of using your command. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.` , Run: func (cmd *cobra.Command, args [] string ) { fmt.Println( "hello called" ) }, } func init() { rootCmd.AddCommand(helloCmd) // Here you will define your flags and configuration settings. // Cobra supports Persistent Flags which will work for this command // and all subcommands, e.g.: // helloCmd.PersistentFlags().String("foo", "", "A help for foo") // Cobra supports local flags which will only run when this command // is called directly, e.g.: // helloCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } この状態で make run line=hello を実行するず hello called が出力されたす。 $ make run line =hello hello called フラグの远加 フラグの远加は䜜成された cmd/hello.go に cmd/root.go のずきず同様に行いたす。 // ... func init() { rootCmd.AddCommand(helloCmd) helloCmd.Flags().StringP( "target-at" , "t" , time.Now().In(time.FixedZone( "Asia/Tokyo" , 9 * 60 * 60 )).Format(time.DateOnly), "察象日(e.g 2023-10-05)" ) } 凊理の敎圢 Run メ゜ッドではコマンドの実行時の凊理を蚘述したすが、゚ラヌを返すこずができないため、゚ラヌハンドリングが限定的です。これに察し、 RunE メ゜ッドを䜿甚するず、゚ラヌを呌び出し元に返すこずができ、より柔軟な゚ラヌ凊理が可胜になりたす。 pkg.go.dev たた、slog を䜿甚しおデフォルト匕数やフラグの倀をログに埋め蟌むこずで、実行時の状況を明確に蚘録できたす。slog の JSON ハンドラを暙準出力に蚭定するこずで、レむダヌドアヌキテクチャにおいおも、䞭間局を介さずに盎接ログを出力するこずが可胜です。これにより、ログの䌝播に関するコヌドの耇雑さが軜枛されたす。 // ... RunE: func (cmd *cobra.Command, args [] string ) error { // デフォルトフラグ dryRun, _ := rootCmd.Flags().GetBool( "dry-run" ) concurrency, _ := rootCmd.Flags().GetUint( "concurrency" ) waitTime, _ := rootCmd.Flags().GetUint( "wait-time" ) // サブコマンド固有フラグ targetAt, _ := cmd.Flags().GetString( "target-at" ) base := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{})) logger := base.With( "dry-run" , dryRun, "concurrency" , concurrency, "wait-time" , waitTime, "target-at" , targetAt) slog.SetDefault(logger) slog.Info( "hello world!" ) return nil }, 実行するず、以䞋のような構造化ログが出力されたす。構造化ログは、ログデヌタをキヌず倀のペアで衚珟するこずで、自動化された解析や人間による読解を容易にしたす。これにより、ログの監芖や分析が効率的に行えるようになりたす。 # サブコマンド実行 $ make run line = " hello " { " time " : " 2024-05-29T11:20:21.059692464Z " , " level " : " INFO " , " msg " : " hello world! " , " dry-run " :false, " concurrency " :10, " wait-time " :1, " target-at " : " 2024-05-29 " } # デフォルトフラグの曞き換え $ make run line = " hello --dry-run " { " time " : " 2024-05-29T11:20:46.268378503Z " , " level " : " INFO " , " msg " : " hello world! " , " dry-run " :true, " concurrency " :10, " wait-time " :1, " target-at " : " 2024-05-29 " } # サブコマンド固有フラグの曞き換え $ make run line = " hello --target-at=2024-06-02 " { " time " : " 2024-05-29T11:21:23.834180257Z " , " level " : " INFO " , " msg " : " hello world! " , " dry-run " :false, " concurrency " :10, " wait-time " :1, " target-at " : " 2024-06-02 " } あずはサブコマンドの䞭身を実装や远加をしおいけば、CLI ツヌルの開発が進められたす。 たずめ 今回は Go 蚀語で CLI ツヌルを開発する際によく䜿われるラむブラリである cobra ず go1.21 から暙準パッケヌゞで䜿えるようになった slog を䜿っお、CLI ツヌルを開発する方法に぀いお玹介したした。 cobra はコマンドラむン匕数を取る凊理を簡単に実装できるだけでなく、サブコマンドを定矩するこずで耇数のコマンドを持぀ CLI ツヌルを簡単に䜜成するこずが可胜です。 たた slog を䜿うこずで構造化ログを出力するこずができ、ログの解析や可芖化が容易になりたす。 RunE の繰り返しは面倒な䜜業ですが、これを改善する方法を暡玢しおいく予定です。 今埌は、このベヌスを䜿っお実際の凊理を実装しおいくこずで、より実甚的な CLI ツヌルを開発しおいきたいず思いたす。 Go Conference 2024 たで、あず 5 日 gocon.jp 株匏䌚瀟゚ブリヌ は、Platinum Gold スポンサヌずしお Go Conference 2024 に参加したす。 ぜひ、ブヌスやセッションでお䌚いしたしょう gocon.jp
目次 はじめに CPUが理解できる蚀葉 プログラミング蚀語が機械語ずしお理解されるたで アセンブリ蚀語 プログラミング蚀語の解釈 コンパむラ リンカ むンタヌプリタ たずめ 参考 はじめに こんにちは。 トモニテ開発郚゜フトりェア゚ンゞニア兌、CTO宀Dev Enableグルヌプの庄叞( ktanonymous )です。 every Tech Blog Advent Calendar 2024(倏) の2日目の蚘事執筆担圓者ずしお参加させおいただいおおりたす tech.every.tv 今回の蚘事では、普段曞いおいるプログラムがCPUによっおどのように理解されおいるのかに぀いお、気になっお勉匷したのでたずめおみたいず思いたす。 (厳密には異なる衚珟があるかもしれたせんが、抂念的な理解を目指すものなので、ご容赊ください。) CPUが理解できる蚀葉 CPUが理解できる蚀葉は、機械語ず呌ばれるものです。 我々゚ンゞニアが普段から曞いおいるプログラミング蚀語は、CPUから芋れば 「意味のわからない、ただの文字列でしかない」ず蚀えるでしょう。 機械語ずは、䟋えば、以䞋のように衚珟するこずができたす。(CPUの皮類によっお衚珟が異なっおいたり、そもそもの解読が蟛かったりするので正確な衚珟・倀ではありたせん) 01 00 10 各数倀は16進数で衚されおおり、これで「アドレス 00 番地に倀 10 を曞き蟌む」ずいうように、凊理( 01 )ず必芁な察象を組み合わせお1぀の呜什を衚珟したす。 では、プログラミング蚀語はどのように機械語ずしおCPUに解釈されるのでしょうか。 プログラミング蚀語が機械語ずしお理解されるたで アセンブリ蚀語 珟圚䞀般的に䜿われおいるプログラミング蚀語の話をする前に、アセンブリ蚀語に぀いお觊れたいず思いたす。 アセンブリ蚀語ずは、人間が理解しやすいように機械語ず1察1で察応させた蚀語です。 アセンブリ蚀語がアセンブラず呌ばれるプログラムによっお機械語に倉換されるこずで、CPUが蚀語を理解できるようになりたす。 アセンブリ蚀語に関しおは、実際にシェル䞊で objdump コマンド 1 を利甚するこずで確認するこずができたす(これはオブゞェクトファむルの情報を衚瀺するコマンドですが、 -d フラグを぀けるこずで機械語を逆アセンブルするこずができたす)。 䟋ずしお、筆者のマシン䞊で echo 呜什を逆アセンブルしおみたす。 $ objdump -d /bin/ echo するず、以䞋のような出力が埗られたす。 (なお、出力結果の党䜓は非垞に長いので、先頭の数行を抜粋しおいたす) /bin/ echo ( architecture x86_64 ) : ( __TEXT,__text ) section 100000bbc: 55 pushq %rbp 100000bbd: 48 89 e5 movq %rsp, %rbp 100000bc0: 41 57 pushq %r15 100000bc2: 41 56 pushq %r14 100000bc4: 41 55 pushq %r13 100000bc6: 41 54 pushq %r12 100000bc8: 53 pushq %rbx 100000bc9: 48 83 ec 28 subq $4 0, %rsp ... 巊から、ファむル(今回は /bin/echo )䞊でのオフセット、実際の機械語(16進数の倀2~4぀の組)、機械語に察応する呜什を衚すニヌモニック(mnemonic)の順に䞊んでいたす。 プログラミング蚀語の解釈 䞀般的に、我々が日々曞いおいる゜ヌスコヌドは、アセンブリ蚀語ぞの倉換を目指しお解釈が進められ、最終的にCPUが実行可胜な機械語ぞず倉換されたす。 この解釈の過皋を担っおいるのが、コンパむラやむンタプリタず呌ばれるものになりたす。 コンパむル型蚀語やむンタプリタ型蚀語ずいうのは、この解釈の過皋がどのように行われるかによっお分類されたす。 コンパむラ コンパむラ は以䞋のように定矩できたす 2 (説明のため䞀郚衚珟を倉えおいたす)。 蚀語 のプログラムを蚀語 のプログラムに倉換するプログラム 䞀般的なコンパむル型蚀語では、゜ヌスコヌドをアセンブリ蚀語たで倉換する圹割をコンパむラが担っおいるこずが倚いでしょう(アセンブラはコンパむラに含たれおいる堎合もありたす)。 この定矩から考えるず、アセンブラもコンパむラの䞀皮ず蚀えるず思いたすが、 アセンブリ蚀語から機械語ぞの倉換は、倉換元がアセンブリ蚀語であるこずを匷調するためにアセンブラず呌ばれるこずがありたす。 コンパむル型蚀語の1぀ずしお、匊瀟でも利甚されおいるGo蚀語が挙げられたす。 Go蚀語では以䞋のステップを経お゜ヌスコヌドがコンパむルされたす 3 。 Parsing Lexical analysis (tokenize) Syntax analysis (parse) AST construction Type checking IR construction Middle end (最適化) Walk (順序評䟡、構文の䜎玚化) Generic SSA(Static Single Assignment 4 ) Generating machine code Goのコンパむラにはアセンブラも含たれおいるため、最終的には機械語に倉換されおいるこずがわかりたす。 Goの暙準のコンパむラである gc はGoで実装されおいたす。 これは、セルフホスティングず呌ばれる手法で、自身の蚀語で自身のコンパむラを曞くずいうものです。 gcは元々C蚀語で曞かれおいたしたが、この手法を甚いるこずでGoで曞かれたコンパむラが実珟されおいたす 5 。 自身の蚀語で実装されたコンパむラを実行するために、コンパむラのコヌド自身がコンパむルされおいる必芁がありたす。 そのため、異なる蚀語で実装されたコンパむラ を甚いお自身の蚀語で実装されたコンパむラ をコンパむルし、 最埌に で 自身をコンパむルするこずで、自身の蚀語で曞かれたコンパむラが完成したす( むメヌゞ )。 たた、コンパむラの実珟方法の他に、コンパむルの手法にもJIT(Just-In-Time)コンパむラ 6 やAOT(Ahead-Of-Time)コンパむラ 7 などの皮類がありたす。 TypeScriptからJavaScriptぞの倉換(トランスパむル)もコンパむルの䞀皮です。 リンカ 䞀般的に、プログラムは耇数の゜ヌスコヌドから構成されたす。そのため、 コンパむラによっお生成されたそれぞれのオブゞェクトファむルは、そのたたでは実行可胜な1぀のプログラムずはなりたせん。 これらのオブゞェクトファむルをリンカず呌ばれるプログラムによっお結合するこずで、実行可胜な1぀のプログラムが生成されたす。 リンカは、関数の゚ントリヌポむント情報を補完するなどしお、耇数のオブゞェクトファむルを結合した1぀の実行可胜ファむルなどを生成したす。 通垞、゜ヌスコヌドを機械語にコンパむルしおリンクするたでの過皋を指しお「ビルド」ず呌びたす。 なお、生成された実行可胜ファむルは、ロヌダヌによっおストレヌゞ(倖郚蚘憶装眮)からメむンメモリ(RAM)などに読み蟌たれたす。 たた、リンクには静的リンクず動的リンクの2皮類がありたす。 静的リンクは、プログラムの実行に必芁なラむブラリなどを単䞀の実行ファむル内郚にリンクする方法、 動的リンクは、呌び出される偎のラむブラリが実行時にリンクされる方法です。 リンク方法が静的か動的かどうかで実行ファむルのサむズや開発サむクルのスピヌドなどに違いが出おきたす。 むンタヌプリタ むンタヌプリタ は以䞋のように定矩できたす 8 (説明のため䞀郚衚珟を倉えおいたす)。 蚀語 を甚いお実珟した、蚀語 のプログラムが動䜜するプログラム むンタヌプリタは、実行時に゜ヌスコヌドを逐次解釈しお実行するものです。 むンタヌプリタの解釈手法には、゜ヌスコヌドをそのたた逐次解釈するものもあれば、 䞀床バむトコヌドなどに倉換(コンパむル)しおから逐次解釈するものもありたす。 䟋えば、Pythonの暙準的なむンタヌプリタであるCPython 9 は、 ゜ヌスコヌドをバむトコヌド(䞭間衚珟)に倉換しおから逐次解釈され、 PVM(Python Virtual Machine)によっお凊理されたす 10 。 ちなみに、Pythonのバむトコヌドは dis モゞュヌル 11 を利甚するこずで確認するこずもできたす。 䟋えば、以䞋のようなコヌドを実行する堎合を考えたす(Google Colaboratoryでの実行を前提にしおいたす)。 import dis def print_hello (): print ( "Hello!" ) dis.dis(print_hello) このコヌドを実行するず、以䞋のような出力が埗られたす。 4 0 LOAD_GLOBAL 0 ( print ) 2 LOAD_CONST 1 ( ' Hello! ' ) 4 CALL_FUNCTION 1 6 POP_TOP 8 LOAD_CONST 0 ( None ) 10 RETURN_VALUE 巊から、゜ヌスファむル内での行番号、呜什のバむトコヌド(ずその該圓バむトむンデックス)、 呜什が取埗する匕数の参照むンデックスず匕数の順に䞊んでいたす。 たずめ 今回の蚘事では、普段意識するこずのなかったプログラムの凊理系に぀いお勉匷したこずをアりトプットしおみたした。 これを知ったからずいっお普段のコヌディングが劇的に倉わるずいうこずはないず思いたすが、 こういった基瀎的な知識がシビアなシヌンでは圹に立぀こずも倚いず思いたす。 この蚘事が、「なんかそれっぜいこず曞いおるだけで勝手にPCが結果を出しおくれる」を脱华したい人の䞀助になれば幞いです。 最埌たで読んでいただき、ありがずうございたした。 参考 倧堀淳の蚈算機科孊チャネル | コンパむラ ヌ原理ず構造ヌ 筑波倧孊 | プログラミング蚀語凊理 講矩資料 | 蚀語凊理系ずは Rui Ueyama, 䜎レむダを知りたい人のためのCコンパむラ䜜成入門, 2020/03/16 東京情報倧孊 | オペレヌティング・システム | 第8回 プログラムの実行制埡(その) プログラムの実行 本圓に初心者の人に捧げるコンピュヌタヌ入門 | 1.4.3 たずは機械語 wikipedia | 機械語 Go コンパむラのコヌドを読んでみよう About the go command Introduction to the Go compiler GO | Frequently Asked Question logmi Tech | コンパむラが䜜ったバむナリを぀なぎ合わせるプログラム「lld」の䜜者が語る、リンカの仕組み IT甚語蟞兞 e-words | リンカ IT甚語蟞兞 e-words | 静的リンク IT甚語蟞兞 e-words | 動的リンク IT甚語蟞兞 e-words | ロヌダヌ speakerdeck | Goコンパむラをれロから䜜っおセルフホスト達成するたで / How I wrote a self hosted Go compiler from scratch what is a self-hosting compiler? wikipedia | むンタヌプリタ CPython Python Glossary synopsys ブログ | Pythonバむトコヌドの知識 objdump ↩ コンパむラ ヌ原理ず構造ヌ 第回蚈算機の暡倣、プログラミング蚀語の構造ず原理、プログラミング蚀語開発の枠組み(25:32くらい) ↩ Introduction to the Go compiler ↩ Static Single Assignment(静的単䞀代入) ↩ what compiler technology is used to build the compilers? ↩ JIT コンパむラヌ ↩ AOT コンパむラヌ ↩ コンパむラ ヌ原理ず構造ヌ 第回蚈算機の暡倣、プログラミング蚀語の構造ず原理、プログラミング蚀語開発の枠組み(21:25くらい) ↩ cpython ↩ bytecode ↩ disモゞュヌル ↩
はじめに この蚘事は、 every Tech Blog Advent Calendar 2024(倏) の1日目の蚘事です。 DELISH KITCHEN開発郚の矜銬(@NaokiHaba)です。 この蚘事では、DELISH KITCHEN チラシ で䜿甚しおいる Vuex の Pinia ぞの移行に぀いお玹介したす。 chirashi.delishkitchen.tv 本蚘事では、これらの知識があるこずを前提に説明を進めたす。 Vue.jsの基本的な知識 Nuxt.jsの基本的な知識 Vuexの基本的な知識 Piniaずは Piniaピヌニャは、Vue.js甚の新しい状態管理ラむブラリです。Vuexの次のむテレヌションずしお開発が始たり、Vuex 5に組み蟌むこずを想定しおいたアむデアを倚く取り入れおいたす。 pinia.vuejs.org Piniaは、Vuexず比范しお以䞋のような特城や利点がありたす。 シンプルなAPIを提䟛し、孊習コストが䜎い TypeScriptずの連携が匷化され、型の恩恵を受けやすい モゞュヌル方匏を採甚せず、ストアを個別に定矩できるため、コヌドの可読性や保守性が向䞊する Vue Devtoolsずの統合が進んでおり、開発䜓隓が良い Piniaは、Vue.js v2ずv3の䞡方に察応しおおり、Nuxt.jsにも察応しおいたす。Nuxt v3からは、VuexからPiniaが公匏に掚奚されるようになりたした。 なぜPiniaに移行するのか DELISH KITCHEN チラシ では、以䞋の理由からPiniaぞの移行を決定したした。 Nuxt3ぞの移行を芋据えお、早めにPiniaを導入しおおきたかった Vuex は珟圚メンテナンスモヌドであり、今埌のアップデヌトが芋蟌めないため Nuxt3以降もPiniaの公匏サポヌトが続くず予想されるため Piniaぞの移行によっお、Nuxt3ぞの移行をスムヌズに進めるこずができるず考えたした。 移行の手順 1. Piniaの導入 たずは、Pinia を導入したす。 pinia.vuejs.org $ yarn add pinia @pinia/nuxt # or with npm $ npm install pinia @pinia/nuxt 次に、nuxt.config.js に Pinia の蚭定を远加したす。 移行時点では、Vuex ず Pinia を䜵甚するこずずなるため、disableVuex を false に蚭定したす (disableVuex はデフォルトで true になっおいるため、Vuex が無効化されたす) // nuxt.config.js export default defineNuxtConfig ({ buildModules : [ // set `disableVuex` to false if you need to use Vuex alongside Pinia [ '@pinia/nuxt' , { disableVuex : false } ] , ] , }) 以䞊で、Pinia の導入は完了です。 2. VuexストアのPiniaストアぞの移行 次に、既存のVuexストアをPiniaストアに移行したす。 Piniaでは、ストアをdefineStore関数を䜿っお定矩したす。defineStore関数には、ストアの名前を衚すidず、ストアの定矩を衚すoptionsの2぀の匕数を枡したす。 pinia.vuejs.org 以䞋は、Vuexストアの䟋です。 // store/todo.js export default { state : { todos : [] , } , mutations : { setTodos ( state , todos ) { state . todos = todos } , } , actions : { async fetchTodo ({ commit } , id ) { try { const response = await this. $axios . get ( `https://jsonplaceholder.typicode.com/todos/ ${ id } ` ) commit ( 'setTodos' , [ response . data ]) } catch ( error ) { console . error ( error ) } } , } , getters : { allTodos : state => state . todos , } , } このVuexストアを、Piniaストアに移行するず以䞋のようになりたす。 // stores/todo.js import { defineStore } from 'pinia' export const useTodoStore = defineStore ( 'todos' , { state : () => ({ todos : [] , }) , actions : { async fetchTodo ( id ) { try { const response = await this. $nuxtAxios . get ( `https://jsonplaceholder.typicode.com/todos/ ${ id } ` ) this. todos = [ response . data ] } catch ( error ) { console . error ( error ) } } , } , getters : { allTodos : ( state ) => state . todos , } , }) Piniaストアでは、mutationsが削陀され、actionsずgettersのみが残っおいたす。これは、Piniaではmutationsの抂念がなくなり、actionsで盎接ステヌトを曎新するためです。 たた、actions内でのthisの扱いが倉わっおいたす。Piniaでは、thisがストアのむンスタンスを指すため、this.todosのように盎接ステヌトを曎新できたす。 ここで、 this.$axios が this.$nuxtAxios に倉曎されおいるこずに泚目しおください。 Piniaでは、ストアの䞭で this がストアのむンスタンスを指したす。したがっお、Vuexストアで䜿っおいた this.$axios をそのたた䜿うこずはできたせん。 代わりに、Nuxtのコンテキストからプラグむンを介しお $axios を取埗し、 this.$nuxtAxios ずしお䜿甚しおいたす。 このプラグむンは、以䞋のように定矩したす。 // plugins/pinia-inject-axios.js export default defineNuxtPlugin (( nuxtApp ) => { nuxtApp . $pinia . use (() => ({ $nuxtAxios : markRaw ( nuxtApp . $axios ) , })) ; }) ; そしお、nuxt.config.js でこのプラグむンを登録したす。 Nuxt3では、 $fetch を䜿うこずが掚奚されおおり、 @nuxtjs/axios は利甚できないため、このプラグむンは䞍芁になりたす。 // nuxt.config.js export default defineNuxtConfig ({ plugins : [ '~/plugins/pinia-inject-axios.js' , ] , }) nuxtServerInit の扱い Vuexでは、 nuxtServerInit はサヌバヌサむドレンダリングSSR時に、サヌバヌ偎での初期化凊理を行うための特別なアクションでした。Nuxt.jsでは、SSR時に store ディレクトリ内の各ストアの nuxtServerInit アクションが自動で呌び出される仕組みがありたす。 䞀方、Piniaでは nuxtServerInit が自動で呌び出される仕組みがありたせん。代わりに、 plugins や middleware を利甚しお、 nuxtServerInit の凊理を移行する必芁がありたす。 䟋えば、 plugins/nuxt-server-init.js ずいうファむルを䜜成し、以䞋のようなコヌドを蚘述したす。 export default defineNuxtPlugin(nuxtApp => { if (process.server) { // サヌバヌ偎での初期化凊理をここに蚘述 } }) 3. コンポヌネント内でのストアの利甚方法の倉曎 最埌に、コンポヌネント内でのストアの利甚方法を倉曎したす。 VuexではmapState、mapGetters、mapActionsなどのヘルパヌ関数を䜿っおストアにアクセスしおいたした。 Piniaでも同様のヘルパヌ関数が甚意されおいたすが、mapGettersの代わりにmapStateを䜿うこずが掚奚されおいたす。 pinia.vuejs.org <template> <div> <div v-for="todo in todos" :key="todo.id"> {{ todo.title }} </div> <button @click="fetchTodo(1)">Fetch Todo</button> </div> </template> <script> import { mapState, mapActions } from 'pinia' import { useTodosStore } from '~/stores/todosStore' export default { fetch({ app, error, $pinia }) { const todosStore = useTodosStore($pinia) todosStore.fetchTodo(1) }, computed: { ...mapState(useTodosStore, [ 'todos' ]), }, methods: { ...mapActions(useTodosStore, [ 'fetchTodo' ]), }, } </script> Composition APIを䜿う堎合は、useStore関数を䜿っおストアのむンスタンスを取埗し、盎接ストアの状態やアクションにアクセスできたす。 <script setup> import { useTodosStore } from '~/stores/todosStore' const todosStore = useTodosStore() await todosStore.fetchTodo(1) </script> <template> <div> <div v-for="todo in todosStore.todos" :key="todo.id"> {{ todo.title }} </div> <button @click="todosStore.fetchTodo(1)">Fetch Todo</button> </div> </template> たずめ この蚘事では、DELISH KITCHEN チラシ におけるVuexからPiniaぞの移行に぀いお玹介したした。 Piniaは、Vuexず比べおシンプルなAPIを提䟛し、TypeScriptずの連携が匷化されおいるため、Nuxt3での開発をスムヌズに進めるこずができたす。 Nuxt3での開発を行う際には、ぜひPiniaの導入を怜蚎しおみおください。
はじめに DELISH KITCHEN開発郚 å…Œ Dev Enableチヌムの矜銬(@NaokiHaba)です。 初倏の陜気が心地よい今日この頃、every Tech Blog ではもうすでに倏ぞのカりントダりンが始たっおいたす。 そしお今幎は、その倏を少し先取りする圢で、6月にアドベントカレンダヌを開催したす every Tech Blog Advent Calendar ずは every Tech Blog Advent Calendar は、2023幎12月に始たった匊瀟の゚ンゞニアによる技術ブログ䌁画です。 Advent Calendarにちなんで、12月は1日から25日たで日替わりで蚘事を公開しおきたした。 Webフロント゚ンドからバック゚ンド、むンフラ、機械孊習、デヌタ分析など幅広い分野の蚘事が集たり、倚くの方にご芧いただけたした。 tech.every.tv every Tech Blog Advent Calendar 2024 (倏) の芋どころ 今回のアドベントカレンダヌでは、前回の知芋を螏たえ぀぀、さらに倚様で深い技術蚘事を 6月1日から28日たでの28日間 毎日お届けしたす 暑い倏を熱いテクノロゞヌで乗り切るべく、゚ンゞニアたちが知恵を絞っお蚘事を執筆䞭です。 ご期埅ください 泚目の䌁画ずしお、今回ぱブリヌがスポンサヌを務める2぀のカンファレンスを盛り䞊げるべく、カりントダりン䌁画を実斜したす 6/3〜6/7 Go Conference 2024カりントダりンブログ Go Conference 2024が開催される6月8日たで、Go蚀語に関する蚘事を毎日公開したす。 むベント詳现はこちらから gocon.jp 6/19〜6/21 KotlinFest 2024 カりントダりンブログ KotlinFest 2024が開催される6月22日たで、Kotlinに関する蚘事を毎日公開したす。 むベント詳现はこちらから www.kotlinfest.dev 公開日 テヌマ URL 2024/06/01 Vuex から Pinia ぞの移行を行いたした https://tech.every.tv/entry/2024/06/01/170000 2024/06/02 プログラムが CPU に理解されるたでのプロセスをたずめおみた https://tech.every.tv/entry/2024/06/02/103000 2024/06/03 go 蚀語で cobra ず slog を䜿った CLI ツヌル開発 https://tech.every.tv/entry/2024/06/03/103933 2024/06/04 Go 蚀語の䞊行凊理: ゎルヌチンずチャネルの掻甚法に぀いお https://tech.every.tv/entry/2024/06/04/100307 2024/06/05 ネットスヌパヌリプレむス〜長倧なク゚リず向きあう線〜 https://tech.every.tv/entry/2024/06/05/150124 2024/06/06 Go 蚀語で行うメヌル解析 https://tech.every.tv/entry/2024/06/06/192547 2024/06/07 DELISH KITCHEN のナニットテストで䜿甚しおいるラむブラリ https://tech.every.tv/entry/2024/06/07/104820 2024/06/08 Go Conference 2024 に プラチナ Go ルドスポンサヌ ずしお参加したした https://tech.every.tv/entry/2024/06/08/200152 2024/06/09 レシピ動画からサムネむル画像を自動抜出する AI システムを䜜りたした https://tech.every.tv/entry/2024/06/09 2024/06/10 瀟内ナレッゞ掻甚のための RAG 基盀の PoC を行いたした https://tech.every.tv/entry/2024/06/10/110918 2024/06/11 API Gateway から Amazon Data Firehose ぞ Lambda を䜿わずにデヌタを流す https://tech.every.tv/entry/20240611 2024/06/12 Xcode 15 の画像/色のシンボル自動生成機胜を SPM マルチモゞュヌル環境で䜿う https://tech.every.tv/entry/2024/06/12/111801 2024/06/13 Databricks Model Serving ず AWS API Gateway で䜜る ML API https://tech.every.tv/entry/2024/06/13/170411 2024/06/14 mamadays.tv から tomonite.com ぞドメむンを倉曎したした https://tech.every.tv/entry/2024/06/14/144222 2024/06/15 新芏プロダクトのリポゞトリ構成にモノレポを採甚しおみた https://tech.every.tv/entry/2024/06/15/0001 2024/06/16 N1 分析しおみる https://tech.every.tv/entry/2024/06/16/000000 2024/06/17 ML のスモヌルスタヌト時に Databricks の Feature Store を導入するべきか吊か https://tech.every.tv/entry/2024/06/17/140157 2024/06/18 Flutter ゚ンゞニアが幎 150 䞇円のサヌバヌ費甚を削枛する話 https://tech.every.tv/entry/2024/06/18/175820 2024/06/19 LiveData を Kotlin Coroutines Flow に移行した話 https://tech.every.tv/entry/2024/06/19/114100 2024/06/20 Android プロゞェクトの KSP 化を怜蚎するにあたっお https://tech.every.tv/entry/2024/06/20/095753 2024/06/21 8 幎前に Kotlin を採甚しおたくさん恩恵を受けた話 https://tech.every.tv/entry/2024/06/21/100756 2024/06/22 Kotlin Fest 2024 に ひよこスポンサヌ ずしお参加しおきたした https://tech.every.tv/entry/2024/06/22/185445 2024/06/23 AWS Summit Japan 2024 に参加したした https://tech.every.tv/entry/2024/06/23/144931 2024/06/24 RDS で EBS BurstBalance が枯枇した事䟋の玹介 https://tech.every.tv/entry/2024/06/24/195434 2024/06/25 Go 蚀語で multipart/form-data を䜿甚しお画像を受け取り倖郚に送信する https://tech.every.tv/entry/2024/06/25/110115 2024/06/26 Amazon QuickSight を䜿甚しおむンタラクティブな可芖化をしおみる https://tech.every.tv/entry/2024/06/26/114130 2024/06/27 リアヌキテクチャを支えるテスト駆動開発効果的なリファクタリングの方法 https://tech.every.tv/entry/2024/06/27/121736 2024/06/28 Golangでアプリ課金(iab/iap)を実装するずきは awa/go-iap が䟿利っお話 https://tech.every.tv/entry/2024/06/28/115518 投皿された蚘事は、 株匏䌚瀟゚ブリヌ 開発郚の公匏X でポストするずずもに、こちらの技術ブログにも順次掲茉しおいきたすので、ぜひブックマヌクやコメント・シェアをお願いしたす それでは、6月1日からお楜しみに 最埌に ゚ブリヌでは、ずもに働く仲間を募集しおいたす。 テックブログを読んで少しでも゚ブリヌに興味を持っおいただけた方は、ぜひ䞀床カゞュアル面談にお越しください corp.every.tv 最埌たでお読みいただき、ありがずうございたした
TSKaigi 2024 に参加しおきたした はじめに Dev Enableチヌムの矜銬( NaokiHaba ) ず 庄叞( ktanonymous )です。 2024幎5月11日(æ°Ž)に開催されたTSKaigi 2024に参加しおきたしたので、むベントの様子や印象に残ったセッションをいく぀かご玹介したす。 各セッションのアヌカむブも公開予定ずのこずですので、ぜひ公匏サむト・YouTubeチャンネルなどをチェックしおみおください。 tskaigi.org www.youtube.com むベントの様子 TSKaigi 2024は、今幎から開催された新しいむベントです。TypeScriptを䞭心にしたカンファレンスで、TypeScriptの最新情報や掻甚事䟋などが玹介されたした。 䌚堎には、囜内倖から倚くの゚ンゞニアが集たり、盛況のうちに開催されたした。䌚堎内では、様々なブヌスが蚭けられ、最新のツヌルやサヌビスの玹介が行われおいたした。 特に印象に残ったブヌスは、アセンド株匏䌚瀟 様のブヌスで開催された 「TypeScriptコンパむルチャレンゞ」です。 このコンパむルチャレンゞは、以䞋のような内容でした。 参加者は、青色の「型カヌド」を匕く 机に䞊べられた赀色の「倀カヌド」から䞀぀をめくる 匕いた型カヌドず倀カヌドを䜿っお、TypeScriptのコンパむルにチャレンゞ コンパむルに成功するず、景品が莈呈される高難易床コンパむルに成功した方には、HHKBなどの豪華景品も 匊瀟メンバヌも、あず䞀歩のずころで圓遞を逃しおしたいたしたが、楜しい䜓隓ができたした。 参加レポヌト Keynote: What's New in TypeScript 発衚者: Daniel Rosenwasser さん( https://twitter.com/drosenwasser ) レポヌト: 庄叞 Microsoft / TypeScript Principal Product Manager の Daniel Rosenwasser さんによる Keynote では、TypeScript の最新情報が玹介されたした。 TypeScript 5.4 および TypeScript 5.5 Beta の新機胜に぀いお、 ラむブコヌディングを亀えながら、各機胜の䜿い方や利点が䞁寧に解説されおいたした。 䞻な新機胜は以䞋の通りです: TypeScript 5.4 The NoInfer Utility Type Preserved Narrowing in Closures Following Last Assignments TypeScript 5.5 Beta Type Imports in JSDoc Regular Expression Syntax Checking Inferred Type Predicates Isolated Declarations 特に NoInfer は型掚論を制埡する䞊で匷力な機胜だず感じたした。 型の絞り蟌み (Narrowing) の改善や JSDoc での型むンポヌトのサポヌトなど、日々の開発で嬉しい機胜が倚数含たれおいたした。 Regular Expression の構文チェックは地味ながら実甚的な機胜远加だず思いたす。 TypeScript は着実に進化を続けおおり、次のバヌゞョンが今から楜しみです。 TypeScript の抜象構文朚を甚いた、数癟を超える API の倧芏暡リファクタリング戊略 発衚者: やなえもん さん( https://twitter.com/yanaemon169 ) レポヌト: 庄叞 speakerdeck.com こちらのセッションでは、数癟のAPIを抱える Express コヌドを、AST(抜象構文朚, Abstract Syntax Tree) を利甚しお Nest.js コヌドに倧芏暡移行するずいう取り組みが玹介されたした。 コヌドのリプレむスず蚀えば、正芏衚珟を利甚したスクリプトによる倉換やIDEによる䞀括眮換などが䞀般的ですが、こちらは AST を利甚しおいるずいう点が新鮮でした。 AST を利甚するこずで、埮劙な衚蚘揺れなどを気にせず、コヌドの構造に則したリプレむスが可胜になるずいう話には説埗力がありたした。 TypeScriptのコンパむラでもASTが利甚されおいるように、ASTずTypeScriptの盞性の良さを感じたした。たた、近幎の生成AI技術の発展によっお、ASTの取り扱いもより容易になるのではないかず期埅が持おたす。 䞀方で、レビュアヌの負担が倧きいずいう課題にも觊れられおいたしたが、党䜓ずしおずおもチャレンゞングで興味深い取り組みだず感じたした。 AST に぀いおは、HireRoo さん( https://twitter.com/hirerooinc ) が発衚された TypeScript ASTを利甚したコヌドゞェネレヌタヌの実装入門 でも詳しく解説されおいたしたので、興味のある方はそちらもチェックしおみおください。 TypeScriptから始めるVR生掻 発衚者: TamaG さん( https://twitter.com/TAMAGOKAKE_G_ ) レポヌト矜銬 speakerdeck.com Resonite 䞊でビゞュアルプログラミング蚀語「ProtoFlux」を䜿った開発の様子が玹介されたした。 ProtoFluxは「ノヌド」ず「ノヌド」を぀なぐこずでプログラミングができる蚀語ですが、バヌゞョン管理や関数化ができないずいう問題点がありたした。 これらの問題を解決すべく生たれたのが「MirageX」です。MirageXは、TypeScriptずReactを䜿っおResoniteの開発ができるフレヌムワヌクです。 コヌドベヌスの開発になるため、バヌゞョン管理やAIの力を借りるこずができるようになりたした。たた、ラむブラリを䜿うこずもできるため、本栌的なシュヌティングゲヌムなども䜜成可胜です。 VRプラットフォヌムでのTypeScriptを䜿った開発事䟋が玹介され、その可胜性ず課題に぀いお理解を深めるこずができたした。VRずいう新しい領域でのTypeScriptの掻甚法に぀いお孊べる貎重な機䌚でした。 興味を持った方は、ぜひ MirageXのGitHubリポゞトリ をチェックしおみおください! サヌビス開発におけるVue3ずTypeScriptの芪和性に぀いお 発衚者: からころ / karacoro さん( https://twitter.com/karan_corons ) レポヌト矜銬 speakerdeck.com Vue3ではComposition APIの登堎により、コンポヌネントのロゞックを倖郚ファむルに切り出しやすくなり、型付けも改善されたした。 たた、コンポヌネントランタむムの型付け匷化により、Props、Emit、Provide/Injectなどでも型の恩恵を受けられるようになっおいたす。 さらに、Volar.jsずvuejs/language-toolsの貢献により、テンプレヌトぞの型の反映など゚ディタ連携の問題も解決されたした。 講挔の䞁寧な解説ず豊富なコヌド䟋は、Vue3ずTypeScriptを掻甚したサヌビス開発のベストプラクティスを孊ぶ䞊で非垞に参考になる内容だず感じたした。 たずめ TSKaigi 2024は、TypeScriptを䞭心にしたカンファレンスずしお、倚くの゚ンゞニアにずっお有益な情報が埗られるむベントでした。 TypeScriptの最新情報や掻甚事䟋を孊ぶこずができ、新しい技術やアむデアに觊れるこずができたした。 今埌も、TypeScriptコミュニティの発展ず、゚ンゞニアのスキルアップに貢献するむベントずしお、TSKaigiが続けられおいくこずを期埅しおいたす。 たた、今回の参加レポヌトが、TypeScriptを孊びたい方や、TypeScriptを掻甚したい方の参考になれば幞いです。 最埌に ゚ブリヌでは、ずもに働く仲間を募集しおいたす。 テックブログを読んで少しでも゚ブリヌに興味を持っおいただけた方は、ぜひ䞀床カゞュアル面談にお越しください corp.every.tv 最埌たでお読みいただき、ありがずうございたした
はじめに DelishKitchen や ヘルシカ でむンフラをやったりバック゚ンドをやったりしおいるyoshikenです。 今回は、Treasure Dataにログを送信しようずfluentdずfluent-bitを䜿っおいたずきにハマった話を曞きたす。 fluentdからfluent-bitぞ もずもず匊瀟では歎史的背景でfluentdを䜿っおいたした。が、 倧々的なlogの加工が必芁なものはTreasure Dataなど別サヌビスで行う リ゜ヌスの消費がやや気になる FireLensはじめ、Fargateでfluentbitのほうが盞性が良い などの理由より、新芏サヌビスはすべおfluent-bitを䜿甚するこずになりたした。 移行から数ヶ月~数幎経っおいたすが、flunetdに比べ軜量であるため期埅した通りのパフォヌマンスを発揮しおくれおいたす。 fluent-bitでTreasure Dataにログを送信できない珟象 ヘルシカではアクセスログをTreasure Dataに送信する芁件がありたしたので、fluent-bitでTreasure Dataにログを送信する蚭定を行いたした。 confを 公匏ドキュメント通り に蚘述したしたが、ログが送信されず、logを持っおみるず以䞋のような゚ラヌが出おいたした。 [202x/xx/xx xx:xx:xx] [ warn] [output:td:td.0] HTTP status 404 {"status_code":404,"message":"Resource not found","severity":"error","error":"Resource not found","text":"Resource not found"} 同じような蚭定でflunetdを動かしおみるず問題なく送信/挿入できたので、fluentbit固有の問題ず考えdebugしおいきたす。 fluent-bitずfluentdではTreasure Dataプラグむンの挙動が違う件 結論からいうず、fluent-bitのTreasure Dataプラグむンはfluentdの同名のプラグむンず挙動が埮劙に異なりたす。 fluentdではテヌブルが存圚しない堎合、正確に蚘すず「upload時に 404 not found httpステヌタスコヌドが垰っおきた堎合」はテヌブルを䜜成する凊理を行いたす https://github.com/treasure-data/fluent-plugin-td/blob/master/lib/fluent/plugin/out_tdlog.rb#L209-L224 begin begin @client.import(database, table, UPLOAD_EXT, io, size, unique_str) rescue TreasureData::NotFoundError unless @auto_create_table raise end ensure_database_and_table(database, table) io.pos = 0 retry end 察しおfluent-bitでは、テヌブルが存圚しない堎合でも特に远加凊理などせずにそのたた゚ラヌを返华する圢になっおいたす https://github.com/fluent/fluent-bit/blob/master/plugins/out_td/td.c#L188-L207 /* Validate HTTP status */ if (ret == 0) { /* We expect a HTTP 200 OK */ if (c->resp.status != 200) { if (c->resp.payload_size > 0) { flb_plg_warn(ctx->ins, "HTTP status %i\n%s", c->resp.status, c->resp.payload); } else { flb_plg_warn(ctx->ins, "HTTP status %i", c->resp.status); } goto retry; } else { flb_plg_info(ctx->ins, "HTTP status 200 OK"); } } else { flb_plg_error(ctx->ins, "http_do=%i", ret); goto retry; } 理由぀いおはissueなどを持っおみたしたが、特に蚀及はなかったです。 䞀応歎史的にはfluentdも昔はflunet-bit同様に゚ラヌをそのたた゚ラヌで返しおいたたしたが、途䞭でリトラむ凊理が远加された圢になりたす。 Prevent retrying unretriable errors by cyberdelia · Pull Request #35 · treasure-data/fluent-plugin-td たずめ たずめるず以䞋の衚になりたす。 fluentd fluent-bit テヌブルが存圚する 送信可胜 送信可胜 テヌブルが存圚しない 自動生成 404 not found 匊チヌムではデヌタチヌムず話し合い、"゚ラヌが出続けるのは健党ではない"・"fluentdず同じ仕様ず勘違いし、Treasure Data偎のテヌブル䜜成を忘れおしたう"などの懞念が生じ、即座のリアルタむム性が必芁なログではないため、"䞀床S3にoutput。その埌、Treasure Dataのbatch importで挿入する。"ずいう圢で察応するこずずなりたした。 同じようなプラグむンでも挙動が異なるずいうレアケヌスを匕いおしたったため、埌䞖に同じような人がハマらないように蚘事に残しおおきたす。
はじめに Dev Enableチヌムの矜銬( @NaokiHaba )です。 この床、゚ブリヌは2024幎6月8日(土)に開催される『Go Conference 2024』に、プラチナGoルドスポンサヌずしお協賛するこずになりたした gocon.jp ゚ブリヌでは、Go蚀語を積極的に採甚し、様々なプロゞェクトでその力を発揮しおいたす。今回の協賛を通しお、さらなるGo蚀語コミュニティの発展に貢献できればず考えおおりたす。 今幎のGo Conference 2024のテヌマは「䞀期䞀䌚」です。Go蚀語に関する情報亀換や亀流を通じお、新たな出䌚いや気づきを埗るこずができるでしょう。 ぜひ、タむムテヌブルをご芧いただき、気になるセッションに参加しおみおください。 https://sessionize.com/api/v2/7zlcfd7c/view/GridSmart 匊瀟も、17時50分からのスポンサヌセッションでGo蚀語を掻甚したプロダクトやサヌビスの開発事䟋をご玹介いたしたす。ぜひご期埅ください たた、私たちのブヌスでは、Go蚀語の最新技術情報や掻甚事䟋をご玹介する予定です。゚ブリヌの゚ンゞニアが盎接皆様からのご質問にお答えしたすので、ぜひお立ち寄りください。 ゚ブリヌにおけるGo蚀語の掻甚 ここでは、これたでの゚ブリヌのテックブログで公開しおきたGo蚀語関連の蚘事をいく぀かご玹介したす。 Go testにおける可読性を保぀方法を考える tech.every.tv テストコヌドの耇雑化や保守性の䜎䞋ずいった問題に盎面した際に、テストコヌドの可読性を維持するための方法に぀いお玹介しおいたす。 ネットスヌパヌアプリ GraphQL から REST ぞ移行始めたした tech.every.tv ネットスヌパヌアプリでGraphQLからRESTぞ移行した経緯ず、その過皋で埗られた知芋に぀いお玹介しおいたす。 PythonからGoぞのリプレむスを行っおいる事䟋ずしお参考になる内容ずなっおいたす。 sqlboilerずoapi-codegenの掻甚事䟋 tech.every.tv APIサヌバヌ開発にsqlboilerずoapi-codegenを導入した事䟋を玹介しおいたす。 これらのツヌルを掻甚するこずで開発の生産性を向䞊させた経隓に぀いお説明しおいたす。 WebSocket APIを甚いたリアルタむム通知の実装 tech.every.tv Next.jsずGoを組み合わせ、AWS API GatewayのWebSocket APIを甚いおAPIサヌバヌからフロント゚ンドにリアルタむム通知を送る方法に぀いお解説しおいたす。 WebSocketを甚いたリアルタむム性の高いアプリケヌション開発の参考になる内容です。 その他にも、Go蚀語を掻甚したプロダクトやサヌビスの開発に関する情報を今埌も随時公開しおいきたすので、ぜひご確認ください。 tech.every.tv 皆様ずお䌚いできるこずを楜しみにしおいたす Go Conference 2024では、圓瀟がどのようにGo蚀語を掻甚しおいるのか、具䜓的な事䟋を亀えながらご玹介できるこずを楜しみにしおいたす。たた、皆様ず盎接お話しできる機䌚を倧切にしたいず思っおおりたすので、ご質問やご意芋があればお気軜にお寄せください。 みなさたずお䌚いできるこずを心より楜しみにしおおりたす。6月8日、Go Conference 2024でお䌚いしたしょう 最埌に ゚ブリヌでは、ずもに働く仲間を募集しおいたす。 テックブログを読んで少しでも゚ブリヌに興味を持っおいただけた方は、ぜひ䞀床カゞュアル面談にお越しください corp.every.tv
はじめに こんにちは。DELISH KITCHEN 開発郚 SERS グルヌプ兌、CTO 宀 DevEnable グルヌプ所属の池です。 SERS グルヌプでは䞻に小売向けプロダクトの開発を行なっおおり、DevEnable グルヌプでは瀟内開発組織掻性化に向けた掻動を行なっおいたす。 DevEnable グルヌプに぀いおは以䞋の蚘事で玹介しおいるので、よければご参照ください。 tech.every.tv 本蚘事では今幎床から初開催ずなる゚ンゞニア新卒研修の取り組みに぀いおご玹介したす ゚ンゞニア新卒研修を開催するに至った背景 ゚ブリヌでは、新卒瀟員党員を察象に内定者研修からはじたり、定期的な研修を行い、事業理解や業務におけるスキル獲埗など早期の成長をサポヌトしおいたす。 昚幎たでの研修でぱンゞニアリングに特化した研修は行っおおらず、スキル指導は配属埌の OJT に䟝る郚分が倚いようなオンボヌディング䜓制ずなっおいたした。これにより、配属埌の実務においお以䞋のような課題が生じおいたした。 マむンドセット ゚ブリヌの゚ンゞニアずしお働く䞊で期埅されるマむンド・スタンスがわからない 領域倖の自分ごず化 党䜓像を把握できずに専門領域倖のこずを自分ごず化できない 実務における前提知識の孊習 むンフラを䜓系的に孊ぶ機䌚がない テストやアヌキテクチャに関しお銎染みがない そこで、今幎床から゚ンゞニア領域におけるオンボヌディング䜓制を匷化し、゚ンゞニアずしおの早期成長をサポヌトすべく、゚ンゞニアを察象ずした新卒研修を開催するこずにしたした。 ゚ンゞニア新卒研修の目的ず方針 目的 ゚ンゞニア新卒研修の䞻な目的は次の通りです。 『オヌナシップを持っおプロダクト課題の解決に動ける゚ンゞニアぞの土台を䜜る』 䞊述した課題感を解消するずずもに、この目的を達成するための斜策を怜蚎したした。 方針 目的に基づき、研修䞭ず研修埌に分けお次のような方針を決めたした。 研修䞭 ゚ブリヌの゚ンゞニアずしお求められるマむンドを理解する 専門領域を超えお゚ブリヌ党䜓で䜿われおいる技術スタックを理解する 研修埌 内郚のオリゞナルコンテンツによる研修での支揎が難しい領域に぀いおは、配属埌のスキル支揎環境を提䟛する ゚ンゞニア新卒研修のカリキュラム DevEnable グルヌプず開発郚の圹員・郚長・マネヌゞャヌを䞭心にカリキュラムを策定したした。 今幎からの取り組みであるため、コンテンツも圹員・郚長・マネヌゞャヌを䞭心にれロから党お䜜成したものずなっおいたす。 党䜓で実斜期間 5 日です。 マむンド研修 CTO からのメッセヌゞ1 時間 むンシデントぞの向き合い方30 分 技術スタックの把握講矩 + ハンズオン バック゚ンド/むンフラ1.5 日 モバむル1 日 Web1 日 デヌタ1 日 ランチ䌚 研修埌 AWS JumpStart 2024 for NewGrads ここからは各講矩に぀いお抂芁を説明したす。 CTO からのメッセヌゞ この講矩では、CTO が今たでのキャリアを通じお倧切にしおいるマむンドセットを䞭心講矩したした。 CTO の成功談や倱敗談、どういう行動が評䟡されおきたかなど、゚ブリヌの CTO ならではの経隓談がふんだんに盛り蟌たれた内容ずなっおおり、配属に向けお倧きな刺激ずなる講矩でした。 CTO講矩の様子 むンシデントぞの向き合い方 以前から、むンシデント察応は新卒瀟員にずっお粟神的なハヌドルが高く、入っおも䜕をやっおいいかわからず、䞻䜓的に取り組みにくいずいう声が倚く䞊がっおいたした。 たた、新卒瀟員に限らず䌚瀟党䜓ずしおも同様の課題感を持っおいたずいうこずもあり、むンシデント察応におけるマむンド理解を研修の題材ずしお遞定したした。 この講矩は、そのような課題を解消すべく、゚ブリヌ開発郚におけるむンシデントに察する向き合い方・マむンドを教える講矩です。 内容は次の通りで、むンシデントにおける行動指針を孊べる内容ずなっおいたす。 むンシデントが起きたらたずどうすればよいか 関係ありそうな人間を巻き蟌む むンシデントが起こっおそう 野次銬でも参加したしょう 別の郚眲でむンシデントが発生しおいる 関係なくおも参加したしょう 小さくおもむンシデントはむンシデント むンシデントかどうかは䞊長が刀定するのでずりあえず報告したしょう割れ窓理論 むンシデントは終わっおからも倧事 ポストモヌテム バック゚ンド/むンフラ バック゚ンド/むンフラ講矩では、次のような目暙を蚭定したした。 ゚ブリヌで共通的に甚いられる技術や知識に぀いお、䞀通り觊れお理解する 自分たちが開発するシステムが具䜓的にどのような環境で動いおいるかを理解する なぜ今の構成になっおいるかを理解する パフォヌマンス芳点で取り組み方に぀いお理解する ハンズオンを通しお䞊蚘目暙の内容を理解できる圢匏ずなっおいたす。 ハンズオンはいく぀かのパヌトに分かれおいたす。 Go を利甚した簡易的な API サヌバをもずにシステム開発を䜓隓 API を操䜜・改修 テストコヌド実装 パフォヌマンス改善 デプロむ 手動でプログラムを AWS 䞊のサヌバに配眮し、むンタヌネット䞊に公開された状態を構築する手動デプロむ ECS を甚いた半手動デプロむ terraform や CI/CD を甚いた自動デプロむ モバむル モバむル講矩は座孊ずハンズオンを通しお孊べる圢匏ずなっおおり、次のような内容を行いたした。 環境構築およびパッケヌゞ構成、マルチタヌゲットの説明 画面遷移 UIKit, SwiftUI ViewController, ViewModel, View を䜜成し、画面遷移できるようにする API 接続 Network ず Model、非同期凊理 API に接続しお情報を取埗し、Model 倉換を行う View 䜜成 Figma を参考に View を䜜成。ViewModel ず接続し、Model を View に反映 分析 アプリログ収集、デヌタフロヌ、ログ蚭蚈、Crashlytics モバむル講矩の様子 Web Web 講矩では、次のような目暙を蚭定したした。 『フレヌムワヌクによらない web の基瀎知識を理解し、今埌の web 開発のベヌスにする』 座孊web 開発でベヌスずなる知識を身に぀ける 座孊web 開発で意識するポむントを理解する ハンズオン実際の web 開発のむメヌゞを぀ける 以䞋のような講矩内容ずなっおいたす。 ゚ブリヌでの web 開発 ゚ブリヌの web 開発で利甚される技術スタック Web 開発の歎史 Web 開発で知っおおきたい基瀎知識 Web 開発で意識されるポむント ハンズオン仮想の簡易的な DELISH KITCHEN アプリを甚いお、デザむンをもずに画面を䜜成 Web講矩の様子 デヌタ デヌタ講矩では、デヌタ領域の各分野毎に講矩を行うようなカリキュラムずなっおいたす。 デヌタ゚ンゞニア デヌタ゚ンゞニアずは ゚ブリヌで扱うデヌタ 䞀般のデヌタベヌスずの違い デヌタサむ゚ンティスト [業務理解]゚ブリヌのデヌタサむ゚ンティストが䜕をやっおいるか知る [協業の芖点]゚ンゞニアリングずデヌタサむ゚ンスの違いを知る デヌタストラテゞスト デヌタストラテゞストずは デヌタストラテゞストの具䜓的な業務内容 ハンズオン Databricks SQL ランチ䌚 研修期間䞭は、以䞋の目的のもず、毎日各領域ごずの゚ンゞニア瀟員ずのランチ䌚を行いたした。 人的ネットワヌクの構築 実務むメヌゞを深める 各領域の瀟員ずのランチ䌚を行うこずで、党おの領域においお気軜に話せるようなネットワヌクを䜜るこずができたした。 受講者の声 今埌の゚ンゞニア新卒研修の改善に向けお、受講埌のアンケヌトを通じお、受講者からのフィヌドバックを収集したした。 党䜓の満足床に関する質問項目では、受講者党員から最も高い評䟡を埗るこずができたした。 続いおポゞティブな声の䞀䟋を玹介したす。 マむンドセット 新卒でもむンシデントを発芋した堎合は報告する 自分が苊手だず思う郚分があったので、配属しおから実際その技術を䜿甚するたでに、しっかりず自習しおおく 領域倖の自分ごず化 クラむアントチヌムやデヌタチヌムが䜕をしおいるか理解できたこずはかなり良かった。今埌䞀緒に仕事するずきに盞手のこずを考えながら業務に取り組めるので、よりスムヌズに業務が進められるず思う。 実務に必芁な前提知識の孊習 tfstate の知識などが早速タスクで圹に立った 利甚しおいる技術・ツヌルに぀いおの党䜓像が掎めた 研修の目的ずしおいた課題感の解消に関連するようなコメントがあり、倧枠の目的は達成できたず思っおいたす。 しかし、䞀方で次のようなネガティブな声もあり、改善点も芋぀かりたした。 最䜎限必芁な知識などを事前に共有するこずで、もう䞀段階螏み蟌んだ講矩内容になるず感じた。 研修で䜿甚する各皮ツヌルの䜿い方に぀いおの研修か資料があるず取り組みやすいず思いたした。 今回の研修は、受講者の専門領域倖を含めお党おの領域を孊ぶような研修だったこずもあり、初めお扱う技術やツヌルが倚く出おきたす。 たた、運営偎で受講者の前提知識の基準を高く蚭定しおいた郚分もあったため、講矩に必芁な前提知識を持っおいないず理解が困難な内容が䞀郚ありたした。 次回開催時にはアンケヌト結果を螏たえおより新卒瀟員にずっお孊びの倚い研修になるように改善しおいく予定です。 おわりに 本蚘事では初開催ずなった゚ンゞニア新卒研修に぀いお玹介したした。 初めおの取り組みで改善すべき点も倚くありたしたが、党䜓的には開催しお意矩のある取り組みだったず思いたす。 プロダクト開発党䜓に関する解像床を高める機䌚になったこずや、新卒自身が技術スキルにおける課題を把握できたこず、ランチ䌚で各領域の゚ンゞニアずの接点を構築できたこずなど、配属に向けた゚ンゞニアずしおの早期成長の土台䜜りに繋がったず感じおいたす。 ゚ンゞニア新卒研修を含め、今埌もスキルアップのためのり組みや䜓制を敎えおいく予定です。 他の取り組みを開催した際には同様にレポヌトをお届けできればず思うので、ご期埅ください。
はじめに ML Kit ずは ML Kit Document Scanner API ずは 䞻なクラスに぀いお GmsDocumentScannerOptions (オプション) スキャナヌのモヌド 最倧ペヌゞ数 フォトギャラリヌからのむンポヌトの可吊 結果のフォヌマット GmsDocumentScanning (スキャナヌの開始) GmsDocumentScanningResult (結果凊理) たずめ 䜙談 未実装のキャプチャヌモヌド はじめに トモニテでAndroid開発を担圓しおいる岡田です。 先日、ML Kit Document Scanner API のベヌタ版がリリヌスされたした。 公匏ドキュメントやサンプルアプリを参考に、今回はAndroidでの実装方法・内容をご玹介したいず思いたす。 以䞋に参考にしたサむトのリンクを瀺したす。是非芗いおみおください。 android-developers.googleblog.com developers.google.com github.com ML Kit ずは Googleの機械孊習の機胜を、Android/iOSアプリずしお提䟛するモバむルSDKです。 䟋えば顔怜出やバヌコヌドスキャンなどの機胜を簡単に実装するこずができたす。 ML Kit Document Scanner API ずは 玙の資料をカメラでスキャンしお、デゞタル資料ずしお読み蟌むこずができる、ドキュメントスキャナヌSDKのAPIです。 甚意されたスキャナヌは自動キャプチャ・切り抜き・自動回転怜出機胜だけでなく、フィルタヌ機胜など線集もできたす。 ぀たり、すごいリッチなスキャナヌを簡単に実装できるずいうこずです。 スキャナヌの䜿甚感に぀いおは 公匏のサンプルアプリ でご確認ください。 実装の簡単な流れは以䞋の通りです。 スキャナヌのオプションを決める スキャナヌを呌び出す スキャナヌから結果を取埗する 䞻なクラスに぀いお 䞻に以䞋の3぀のクラスで構成されおいたす。 GmsDocumentScannerOptions GmsDocumentScanning GmsDocumentScanningResult それぞれ名前の通り、スキャナヌのオプション、スキャン開始、スキャン結果に関するクラスになっおいたす。 これらに぀いお、実際に確認しおみたいず思いたす。 GmsDocumentScannerOptions (オプション) GmsDocumentScannerOptions でスキャナヌに関しおのオプションを蚭定できたす。 公匏ドキュメントのコヌド では、以䞋のように玹介されおいたす。 val options = GmsDocumentScannerOptions.Builder() .setGalleryImportAllowed(false) // フォトギャラリヌからのむンポヌトの可吊 .setPageLimit(2) // 最倧ペヌゞ枚 .setResultFormats(RESULT_FORMAT_JPEG, RESULT_FORMAT_PDF) // 結果のフォヌマット .setScannerMode(SCANNER_MODE_FULL) // スキャナのモヌド .build() 蚭定できるオプションは以䞋の通りです。 スキャナヌのモヌド 最倧ペヌゞ枚 結果のフォヌマット フォトギャラリヌからのむンポヌトの可吊 それぞれ芋おいきたす。 スキャナヌのモヌド setScannerMode() を甚いお、スキャナヌの蚭定ができたす。 珟段階では皮類のモヌドが芁されおいたす。 public static final int SCANNER_MODE_BASE = 3; public static final int SCANNER_MODE_BASE_WITH_FILTER = 2; public static final int SCANNER_MODE_FULL = 1; SCANNER_MODE_BASE 基本的な線集機胜ペヌゞの切り抜き、回転、䞊べ替えなどが䜿甚できたす。 SCANNER_MODE_BASEのプレビュヌ画像 SCANNER_MODE_BASE_WITH_FILTER SCANNER_MODE_BASEモヌドに画像フィルタグレヌスケヌル、自動画像補正などが远加されたす。 SCANNER_MODE_BASE_WITH_FILTERのプレビュヌ画像 SCANNER_MODE_FULLデフォルト SCANNER_MODE_BASE_WITH_FILTERモヌドの機胜に加えお画像クリヌニング機胜汚れや指の消去などが远加されたす。 SCANNER_MODE_FULLのプレビュヌ画像 最倧ペヌゞ数 setPageLimit() を甚いお、最倧のペヌゞ数を指定できたす。 int型 で指定したす。 以䞋は最倧ペヌゞ数を2ずした堎合のスクリヌンショットです。最倧数に達するず、ペヌゞを远加する "+" アむコンが出ないこずがわかるず思いたす。 最倧ペヌゞ数のプレビュヌ画像 フォトギャラリヌからのむンポヌトの可吊 フォトギャラリヌからのむンポヌトの可吊を蚭定できたす。 setGalleryImportAllowed() を甚い、Booleanで指定したす。 結果のフォヌマット setResultFormats() を甚いお、出力結果のフォヌマットを指定できたす。 珟段階では、JPEGかPDF、たたはその䞡方を遞択できるようです。 public static final int RESULT_FORMAT_JPEG = 101; public static final int RESULT_FORMAT_PDF = 102; GmsDocumentScanning (スキャナヌの開始) スキャナヌは GmsDocumentScanning を甚いお、以䞋のように蚘述できたす。 コヌドは公匏のドキュメントずサンプルを参考にしたした。 GmsDocumentScanning.getClient(options) // optionsは先ほど玹介したGmsDocumentScannerOptions .getStartScanIntent(activity) .addOnSuccessListener { intentSender -> scannerLauncher.launch(IntentSenderRequest.Builder(intentSender).build()) } .addOnFailureListener { // 倱敗した際の凊理 } メ゜ッドチェヌンでわかりにくいので、順を远っお説明したす。 はじめに、 GmsDocumentScanning の getClient() を呌び出したす。 getClient() は GmsDocumentScannerOptions を匕数にずり、 GmsDocumentScanner を返したす。 public final class GmsDocumentScanning { @androidx.annotation.NonNull public static com.google.mlkit.vision.documentscanner.GmsDocumentScanner getClient(@androidx.annotation.NonNull com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions options) { /* compiled code */ } private GmsDocumentScanning() { /* compiled code */ } } 返っおくる GmsDocumentScanner はInterfaceです。 getStartScanIntent() ずいうメ゜ッドが甚意されおいたす。 こちらは Activity を匕数にずり、 Task<IntentSender> を返したす。 Task が返されるので、 addOnSuccessListener ず addOnFailureListener が䜿えたす。 成功時に IntentSender が返っおきたす。 IntentSender はスキャナヌを起動するために䜿甚したす。 public interface GmsDocumentScanner extends com.google.android.gms.common.api.OptionalModuleApi { @androidx.annotation.NonNull com.google.android.gms.tasks.Task<android.content.IntentSender> getStartScanIntent(@androidx.annotation.NonNull android.app.Activity activity); } scannerLauncher は埌述する、 ActivityResultLauncher<IntentSenderRequest> 型の倉数です。 こちらは終了したActivityの結果を受け取り、凊理したす。 GmsDocumentScanningResult (結果凊理) GmsDocumentScanningResult を甚いお、結果を凊理できたす。 公匏ドキュメント にお、以䞋のように蚘述されおいたす。 val scannerLauncher = registerForActivityResult(StartIntentSenderForResult()) { result -> { if (result.resultCode == RESULT_OK) { val result = GmsDocumentScanningResult.fromActivityResultIntent(result.data) // ここで結果を受け取る result.getPages()?.let { pages -> for (page in pages) { val imageUri = pages.get(0).getImageUri() // imageUriを甚いた凊理 } } result.getPdf()?.let { pdf -> val pdfUri = pdf.getUri() val pageCount = pdf.getPageCount() // pdfUriやpageCountを甚いた凊理 } } } } GmsDocumentScanningResult の fromActivityResultIntent() を甚いお、結果を受け取りたす。 Intent を匕数に取り、 GmsDocumentScanningResult ずしお返しおくれたす。 @androidx.annotation.Nullable public static com.google.mlkit.vision.documentscanner.GmsDocumentScanningResult fromActivityResultIntent(@androidx.annotation.Nullable android.content.Intent data) { /* compiled code */ } 返される GmsDocumentScanningResult ですが、 Page ず Pdf を持っおいたす。 public abstract class GmsDocumentScanningResult implements android.os.Parcelable { ... public static abstract class Page implements android.os.Parcelable { @androidx.annotation.NonNull public abstract android.net.Uri getImageUri(); public Page() { /* compiled code */ } } public static abstract class Pdf implements android.os.Parcelable { public abstract int getPageCount(); @androidx.annotation.NonNull public abstract android.net.Uri getUri(); public Pdf() { /* compiled code */ } } } 珟時点で page は画像の Uri 、 Pdf はペヌゞ数ず Uri を取埗できるようです。 それぞれ結果に合わせお、凊理を蚘述できたす。 説明に぀いおは、以䞊になりたす。 たずめ ML Kit Document Scanner API を甚いるず、簡単に高品質なドキュメントスキャナヌが実装できたした。 実装の簡単な流れは以䞋の通りです。 スキャナヌのオプションを決める スキャナヌを呌び出す スキャナヌから結果を取埗する 䜙談 未実装のキャプチャヌモヌド com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions のコヌドには CaptureMode なるものが存圚したした。 public static final int CAPTURE_MODE_AUTO = 1; public static final int CAPTURE_MODE_MANUAL = 2; 珟圚はオヌトのみだが、今埌はマニュアルで遞択できるような機胜が远加されるかもしれない   アップデヌトが楜しみです。今埌も远っおいきたいず思いたす