PostgreSQLとRustでRESTサービスを作成します。 パート1:プロトタイプ

みなさんこんにちは。



少し前に、悪名高いブログで、Goでデータベースを操作する簡単なプログラムを実装し、それに基づいてRESTサービス全体を作成する方法についての投稿を見ました。 Rustで同様のプログラムを作成し、結果を共有することの難しさを確認することにしました。







まず、データベースを操作し、通常のコンソールアプリケーションを作成してから、いわばRESTフロントエンドを追加します。



いくつかの紹介の言葉。



せっかちな人のために、GitHubで完成したプロジェクトを以下に示します。 RESTサービスの実装が含まれます。 私は他の皆にさらに読むように勧めます。



一般に、Rustに関するすべてのエラー、その原因、および解決策とともに、開発プロセスを詳細に説明しようとします。 典型的な問題とそれを解決する方法を知ることは、この言語の初心者にとって大いに役立つと思います。 コンパイラを恐れないでください、彼はあなたの友達です。



Rust をインストールする必要があります( インストール方法 )。 1.0以降のすべてのバージョンが動作するはずです-安定版と夜間版の両方。 1.1〜1.3の範囲でいくつか試しました。



コード自体はプロトタイプ品質です-今のところ、非常に信頼性の高い、または読みやすいプログラムを作成しようとはしていません。 それを理解したら、後で正確さとスタイルについて考えることが可能になります。 ただし、このバージョンは非常に迅速に作成されました。



ここまでがポイントです。



アセンブリで特別なトリックを必要としないRustプロジェクトと同様に、プログラムではCargoを使用します。 新しいプロジェクトを作成します。



 $ cargo new --bin rust-phonebook
 $ cd rust-phonebook


Cargoは、ディレクトリにGitリポジトリを慎重に作成します。



それはどのように見えますか
 $ gitステータス

ブランチマスター上

初期コミット

追跡されていないファイル:
   (コミットする内容に含めるには、「git add <file> ...」を使用します)

	 .gitignore
	 Cargo.toml
	 src /

コミットには何も追加されませんが、追跡されていないファイルが存在します(追跡するには「git add」を使用します)




そして、すぐにスタブプログラムをビルドして実行できます。



 $カーゴラン

    rust-phonebook v0.1.0のコンパイル(ファイル:/// home / mkpankov / rust-phonebook)
      `target / debug / rust-phonebook`の実行
こんにちは世界!


次に、リポジトリに変更をコミットし、プログラムの本質に進みます。






データベースに接続し、1つのテーブルを作成し、そこに1つのレコードを追加し、それを読み戻す最も単純なプロトタイプから始めましょう。



最初にコード全体を提供し、次に各部分を説明します。 以下はsrc / main.rsの内容です。



コード
extern crate postgres; use postgres::{Connection, SslMode}; struct Person { id: i32, name: String, data: Option<Vec<u8>> } fn main() { let conn = Connection::connect( "postgres://postgres:postgres@localhost", &SslMode::None) .unwrap(); conn.execute( "CREATE TABLE person ( id SERIAL PRIMARY KEY, name VARCHAR NOT NULL, data BYTEA )", &[]) .unwrap(); let me = Person { id: 0, name: "".to_string(), data: None }; conn.execute( "INSERT INTO person (name, data) VALUES ($1, $2)", &[&me.name, &me.data]) .unwrap(); let stmt = conn.prepare("SELECT id, name, data FROM person").unwrap(); for row in stmt.query(&[]).unwrap() { let person = Person { id: row.get(0), name: row.get(1), data: row.get(2) }; println!(" : {}", person.name); } }
      
      







順番に見てみましょう。



 fn main() { let conn = Connection::connect( "postgres://postgres:postgres@localhost", &SslMode::None) .unwrap();
      
      





新しいメインの最初の行は、データベースへの接続です。 ここで、すぐに詳しく説明する価値があります。



PostgreSQLサーバーはデフォルトポートでローカルに実行されており、ユーザー名とパスワードは「postgres」であると想定しています。 もちろん、これを行うには、PostgreSQLをインストールする必要があります。 たとえば、 このガイドをご覧ください。 「postgres:postgres」ではなく、データベースにアクセスするユーザー名とパスワードを指定します。



さらに、データベースの初期化を忘れないでください。



前述のConnection自体は、postgresコンテナのタイプです( ドキュメント )。 したがって、ファイルの先頭でバインディングを要求します



 extern crate postgres;
      
      





ConnectionおよびSslModeのスコープに入ります



 use postgres::{Connection, SslMode};
      
      





今すぐプログラムをビルドしようとすると、別のエラーが発生します。



 $カーゴビルド
    rust-phonebook v0.1.0のコンパイル(ファイル:///home/mkpankov/rust-phonebook.finished)
 src / main.rs:1:1:1:23エラー: `postgres`のクレートが見つかりません
 src / main.rs:1つの外部クレートpostgres。
               ^ ~~~~~~~~~~~~~~~~~~~~~〜
エラー:前のエラーのため中止
 「rust-phonebook」をコンパイルできませんでした。

詳細については、-verboseを使用してコマンドを再度実行してください。


これは、コンパイラが適切なコンテナを見つけられなかったことを意味します。 これは、プロジェクトの依存関係で指定しなかったためです。 Cargo.tomlでそれをしましょう( 詳細 ):



 [依存関係]

 postgres = "0.9"


これですべてが収集されます。 ただし、サーバーを起動しなかった場合は、次のエラーが表示されます。



 
 $カーゴラン
      `target / debug / rust-phonebook`の実行
スレッド '<main>'は、 `Err`値で '呼び出された` Result :: unwrap() `でパニックになりました:IoError(エラー{repr:Os {code:111、message:" Connection refused "}})'、.. /src/libcore/result.rs:732


これは.unwrap()の直接の結果です-結果がOk(_)でない場合、現在のスレッドのパニックを引き起こします-つまり 接続エラーが発生しました。



ところで、環境でRUST_BACKTRACE = 1を設定してプログラムを実行すると、そのバックトレースを見ることができます(プログラムのデバッグバージョンでのみ動作します!)。



バックトレース
 $ RUST_BACKTRACE = 1カーゴラン

      `target / debug / rust-phonebook`の実行
スレッド '<main>'は、 `Err`値で '呼び出された` Result :: unwrap() `でパニックになりました:IoError(エラー{repr:Os {code:111、message:" Connection refused "}})'、.. /src/libcore/result.rs:732
スタックバックトレース:
    1:0x56007b30a95e-sys ::バックトレース::書き込み:: haf6e4e635ac76143Ivs
    2:0x56007b30df06-パニック状態:: on_panic :: ha085a58a08f78856lzx
    3:0x56007b3049ae-rt ::巻き戻し:: begin_unwind_inner :: hc90ee27246f12475C0w
    4:0x56007b304ee6-rt ::巻き戻し:: begin_unwind_fmt :: ha4be06289e0df3dbIZw
    5:0x56007b30d8d6-rust_begin_unwind
    6:0x56007b3390c4-パニック::: panic_fmt :: he7875691f9cbe589SgC
    7:0x56007b25e58d-結果::結果<T、E> ::展開:: h10659124002062427088
                         ../src/libcore/macros.rs:28で
    8:0x56007b25dcfd-メイン:: h2f2e9aa4b99bad67saa
                         src / main.rsで:13
    9:0x56007b30d82d-__rust_try
   10:0x56007b30fbca-rt :: lang_start :: hefba4015e797c325hux
   11:0x56007b27d1ab-メイン
   12:0x7fb3f21076ff-__libc_start_main
   13:0x56007b25db48-_start
   14:0x0-<不明>




ふう、たった1行で、非常に多くの方法で混乱します! あなたがあまり怖くなくて、継続する準備ができていることを願っています。



ここでの良い点は、接続が失敗した場合にプログラムをドロップすることを明示的に言うことです。 おもちゃから通常の製品を作りたいときは、.unwrap()で簡単なテキスト検索を行うと、どこから始めればよいかがわかります。 さらに、私はこの瞬間にこだわらない。



テーブルを作成します。



  conn.execute( "CREATE TABLE person ( id SERIAL PRIMARY KEY, name VARCHAR NOT NULL, data BYTEA )", &[]) .unwrap();
      
      





奇妙な&[]は最後に空のスライスです。 このリクエストにはパラメータがないため、それらを渡しません。



なぜアレイではなくスライスなのか? Rustの良いスタイルは、オブジェクトが読み取り専用の場合、所有権を受け入れないことです。 それ以外の場合、関数に渡す値を複製する必要があります。 彼女はそれを「飲み込む」でしょう。 所有権についてはこちらをご覧ください



次に、レコードを表す構造を作成し、テーブルに追加します。



  let me = Person { id: 0, name: "".to_string(), data: None };
      
      





基本的に、このデータを構造に追加することは意味がありませんが、さらに役立ちます。 ところで、ここに彼女の発表があります:



 struct Person { id: i32, name: String, data: Option<Vec<u8>> }
      
      





次に、実際の挿入を実行します。



  conn.execute( "INSERT INTO person (name, data) VALUES ($1, $2)", &[&me.name, &me.data]) .unwrap();
      
      





ここで、リクエストにはすでにパラメータがあります。 これらは、番号付きフィールド$ 1、$ 2などで文字列補間を使用して置換されます。 そして今、私たちのパラメータスライスは空ではありません-それは対応する構造フィールドへのリンクが含まれています。



次に、データベースへのクエリを準備して、作成した内容を読み取ります。



  let stmt = conn.prepare("SELECT id, name, data FROM person").unwrap();
      
      





何も面白いとは思いません。 要求オブジェクトを作成しているだけです。 クエリの重複は、パフォーマンスを向上させるために、再作成するのではなく、保存するのに意味があります。 「準備済みオブジェクト」を作成せずに、すぐにリクエストを実行することもできます。



最後に、リクエスト自体を実行します。 各行を見ていきましょう。



  for row in stmt.query(&[]).unwrap() {
      
      





ここでは、クエリ結果の配列を調べます。 いつものように、リクエストは失敗する可能性があります。 パラメータリストは再び空になります-&[]。



ここでも、クエリ結果から構造を収集します。



  let person = Person { id: row.get(0), name: row.get(1), data: row.get(2) };
      
      





ここでは、フィールドを数字で取得しますが、一般的にライブラリではテーブルフィールドの名前を使用できます。



最後に、結果とともにメッセージを印刷します。



  println!(" : {}", person.name); } }
      
      





インフラストラクチャ、用語、環境の設定に精通していたため、投稿は長くなりましたが、ワークフローの実例として役立つことを願っています。



次の部分では、サーバー構成を.iniファイルに追加します。 一緒にいてください!



All Articles