👄

Dockerで通信の流れを学んだ

2024/07/03に公開

Dockerの仕組みを学ぼうと思い、以下のサイトを参考にしてシンプルなプロジェクトにとりくんだ

https://zenn.dev/k41531/articles/054a0e30d49f19

学んだこと

  • Dockerの構成
  • 3つの間での通信方法
    1. WebページとWebサーバー間
    2. WebサーバーとバックエンドAPI間
    3. バックエンドサーバーとAPI間

今回のスタック

  • Docker - バックエンドとフロントエンドのネットワークを共有する
  • Express.js - Webサーバーとして、バックエンドAPIとの通信を可能にする
  • Flask - Flask APIを使用して、APIとの通信を可能にする
  • DEEPL API - 翻訳したテキストをバックエンドAPIに返す

Dockerの構成

Dockerfile

フロントエンド用のサーバーとバックエンド用のサーバーを用意する必要があります。
バックエンド・フロントエンドを扱っているそれぞれのフォルダのルートにそれぞれDockerfileを作成する必要があります。
以下のサイトのDockerfileを参考にしました。
https://zenn.dev/k41531/articles/054a0e30d49f19

ここでは、ローカルの環境をDockerの仮想環境にコピーするために必要なものを指定しています。

  • スタック
  • ディレクトリ
  • コード
  • コマンド
  • ポート番号

docker-compose.yml

このファイルを使用して、フロントエンドとバックエンドが共有のインターネットを使用できるようにします。なので、このファイルの配置は、プロジェクトのルートに位置しないといけません。

version: '3.8'
services:
  front:
    build:
      context: ./front
    ports:
      - 3000:3000
    networks:
      - app-network
    depends_on:
      - back

  back:
    build:
      context: ./back
    ports:
      - 4000:4000
    environment:
      - DEEPL_API_KEY=${DEEPL_API_KEY}
    networks:
      - app-network

networks:
  app-network:
    driver: bridge  

1. 共有ネットワーク

app-networkが共有ネットワークとして設定されています。これにより、frontbackのサービスが同じネットワークに所属し、互いにアクセス可能になります。

yamlコードをコピーする
networks:
  app-network:
    driver: bridge

2. ネットワークドライバ

driver: bridgeはDockerのブリッジネットワークを使用することを示しています。ブリッジネットワークは、同じDockerネットワーク内のコンテナ間での通信を可能にします。

3. サービス間の通信

フロントエンドからバックエンドへの通信

  • フロントエンドのExpress.jsサーバーがバックエンドのFlask APIにリクエストを送信する際、バックエンドサービスのホスト名backを使用します。
  • これは、app-network内での名前解決により、http://back:4000のようにアクセス可能になります。

3つの間での通信方法

自分なりの今回のプロジェクトの通信の流れを図にしてみました

通信の流れ

  1. index.htmlとWebサーバー(express.js)間

    • ユーザーがブラウザ上でindex.htmlを表示し、ボタンをクリックすると、ブラウザはExpress.jsサーバーにPOSTリクエストを送信します。
    • Express.jsサーバーはこのリクエストを受け取り、適切なレスポンスを返します。
  2. Webサーバー(express.js)とバックエンドAPI(Flask API)間

    • Express.jsサーバーはaxiosライブラリを使用して、バックエンドのFlask APIに対してGETリクエストを送信します。
    • Flask APIは、このリクエストを受け取り、処理を行います。
  3. バックエンドAPI(Flask API)とDEEPL API間

    • Flask APIは、DeepL APIに対してテキストの翻訳リクエストを送信します(GETリクエスト)。
    • DeepL APIはリクエストを処理し、翻訳されたテキストをFlask APIに返します。

ファイルを見てみよう

front/public/index.html

<!DOCTYPE html>
<html>
    <head>
        <title>Hello World Page</title>
        <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.16/dist/tailwind.min.css" rel="stylesheet">
        #htmxを使用している
        <script src="https://unpkg.com/htmx.org@1.6.1"></script>
    </head>
    <body class="grid items-center justify-items-center h-screen gap-4 bg-gray-200">

#ここにレスポンスで帰ってきたテキストがくる
        <div id="response" class="text-center">Waiting for response...</div>

#hx-post="/api"によってwebサーバー(express.js)にPostリクエストが送られる
        <button class="px-4 py-2 font-bold text-white bg-blue-500 rounded hover:bg-blue-700" hx-post="/api" hx-trigger="click" hx-swap="innerHTML" hx-target="#response">Click me!</button>
    </body>
</html>

front/index.js

const express = require('express');
const axios = require('axios');

const app = express();
const port = 3000;

app.use(express.static('public')); // 静的ファイルを提供

// フロントエンドからのAPIリクエストを受け取る
app.post('/api', async (req, res) => {
  try {
#axiosというライブラリを使用し、バックエンドに Getリクエストを送る
    const response = await axios.get('http://back:4000');
    console.log(response.data.translated_text)

#レスポンスをwebページに返す
    res.send(response.data.translated_text);
  } catch (error) {
    console.error(`Error: ${error}`);
    res.status(500).send('Internal Server Error');
  }
});

app.listen(port, () => {
    console.log(`App running on http://localhost:${port}`);
})

back/app.py

from flask import Flask, Response, request
import deepl
import os
import json

app = Flask(__name__)

#getリクエストをDeepl Apiに送る
@app.route('/', methods=['GET'])

def translate():
    deepl_api_key = os.getenv('DEEPL_API_KEY')
    translator = deepl.Translator(deepl_api_key)

    #request.args.getを使用して、クエリパラメータから翻訳するテキストとターゲット言語を取得しています。
    text = request.args.get('text', default="Hello, how are you?", type=str)
    target_lang = request.args.get('target_lang', default='JA', type=str)

    result = translator.translate_text(text, target_lang=target_lang)

    
    response_data = {'translated_text': result.text}
    response_json = json.dumps(response_data, ensure_ascii=False)
    
    return Response(response_json, content_type='application/json; charset=utf-8')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=4000)

まとめ

動けばいいとか、他の人のコードを真似してできたっていう感じで開発していたから、エラーが出てきた時にどこに問題があるのかを見極めるのに時間がかかった。今回通信の方法を理解することができたので、今後の開発で問題の原因を以前よりも早く見つけられそう。

次は、データベースサーバーがここに加わるとどうなるのかについて勉強したい。

Discussion