Java スタートガイド

このチュートリアルは、Google Cloud の導入において主なアプリ開発のコンセプトを学習したいエンジニアやウェブ開発者など、クラウドにアプリをビルドするのが初めての人を対象にしています。

目標

アプリのビルドに関する言語固有の他のチュートリアルについては、以下のガイドをご覧ください。

料金

このチュートリアルでは、課金対象である次の Google Cloud コンポーネントを使用します。

このチュートリアルでは、Google Cloud の Always Free 枠の制限内でリソースの使用が維持されるよう設計されています。 料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。 新しい Google Cloud ユーザーは無料トライアルをご利用いただける場合があります。

このチュートリアルを終了した後、作成したリソースを削除すると、それ以上の請求は発生しません。詳しくは、クリーンアップをご覧ください。

始める前に

  1. Google アカウントにログインします。

    Google アカウントをまだお持ちでない場合は、新しいアカウントを登録します。

  2. Cloud Console のプロジェクト セレクタページで、Cloud プロジェクトを選択または作成します。

    プロジェクト セレクタのページに移動

  3. Google Cloud プロジェクトに対して課金が有効になっていることを確認します。 プロジェクトに対して課金が有効になっていることを確認する方法を学習する

  4. Firestore データベースをネイティブ モードで作成するには、次の手順に沿って操作してください。
    1. Cloud Console で、[Firestore Viewer] ページに移動します。
      Firestore Viewer に移動
    2. [Firestore モードの選択] 画面から、[ネイティブ モードを選択] をクリックします。
    3. Firestore データベースのロケーションを選択します。このロケーション設定は、Cloud プロジェクトのデフォルトの Google Cloud リソースのロケーションです。このロケーションは、特にデフォルトの Cloud Storage バケットおよび App Engine アプリの、ロケーション設定を必要とする Cloud プロジェクト内の Google Cloud サービスで使用されます。
    4. [データベースを作成] をクリックします。
  5. App Engine Admin, Cloud Storage, Cloud Logging, and Error Reporting API を有効にします。

    API を有効にする

  6. Cloud Shell で、アプリのソースコードを開きます。
    Cloud Shell に移動

    Cloud Shell を使用すると、ブラウザからコマンドラインで直接 Google Cloud リソースにアクセスできます。

  7. サンプルコードをダウンロードしてアプリのディレクトリに移動するには、[続行] をクリックします。
  8. Cloud Shell で、gcloud ツールを構成して新しい Google Cloud プロジェクトを使用します。

        # Configure gcloud for your project
        gcloud config set project PROJECT_ID
        

    PROJECT_ID を、Cloud Console を使用して作成した Google Cloud プロジェクト ID に置き換えます。

    gcloudコマンドライン ツールは、コマンドラインから Google Cloud リソースを操作する基本的な方法です。このチュートリアルでは、gcloud ツールを使用してアプリをデプロイおよびモニタリングします。

アプリを実行する

  1. すでに Cloud Shell を使用していて、Java 11 を使用するように構成している場合は、シェルの Java 代替、JAVA_HOMEPATH 環境変数を更新して Java 8 指定します。
  2. 1-cloud-run ディレクトリに変更してアプリを実行します。
        GOOGLE_CLOUD_PROJECT=PROJECT_ID mvn -Plocal clean jetty:run-exploded
        
    PROJECT_ID は、作成した Google Cloud プロジェクト ID で置き換えます。
  3. Cloud Shell で、[ウェブでプレビュー] をクリックし、[ポート 8080 でプレビュー] を選択します。新しいウィンドウが開き、実行中のアプリが表示されます。

アプリを Cloud Run にデプロイする

Google Cloud には、コードを実行するためのいくつかのオプションが用意されています。この例では、Cloud Run を使用してスケーラブルなアプリを Google Cloud にデプロイします。Cloud Run ではサーバーの管理が不要なため、コーディングに集中できます。また、トラフィックが急激に跳ね上がった場合も Cloud Run が自動的にスケーリングしてサポートします。

  1. Jib を使用してイメージをビルドします。
    mvn package jib:build -Dimage gcr.io/PROJECT_ID/bookshelf

    PROJECT_ID は、作成した Google Cloud プロジェクト ID で置き換えます。

  2. 次にイメージをデプロイします。
    gcloud run deploy bookshelf --image gcr.io/PROJECT_ID/bookshelf \
        --platform managed --region us-central1 --allow-unauthenticated
    PROJECT_ID は、作成した Google Cloud プロジェクト ID で置き換えます。

デプロイが成功すると、Cloud Run で実行しているアプリに次のフォーマットでエンドポイントを出力します。

https://bookshelf-abcdefghij-uc.a.run.app

アプリはこのリンク(以降 YOUR_CODE_RUN_URL)で表示できるようになりました。ウェブブラウザにアドレスを入力すると、アプリが表示されます。

Bookshelf アプリのホームページ

Firestore でデータを永続化する

App Engine インスタンスに情報を保存することはできません。これは、インスタンスが再起動された場合に情報は失われ、新しいインスタンスを作成した際に情報は存在しないためです。代わりに、すべてのインスタンスが読み取りおよび書き込みするデータベースを使用します。

Google Cloud にはデータ保存のためのいくつかのオプションがあります。この例では、Firestore を使用して各書籍のデータを保存します。Firestore はフルマネージドでサーバーレスの NoSQL ドキュメント データベースであり、データを保存および照会できます。Firestore はアプリのニーズを満たすために自動でスケーリングし、使用しない場合はゼロにスケーリングします。最初の書籍を追加します。

  1. ウェブブラウザに次のアドレスを入力します。

    https://PROJECT_ID.REGION_ID.r.appspot.com

    以下を置き換えます。

  2. デプロイしたアプリ用の書籍を作成するには、[Add book] をクリックします。

    Bookshelf アプリに書籍を追加する
  3. [Title] フィールドに「Moby Dick」と入力します。
  4. [Author] フィールドに「Herman Melville」と入力します。
  5. [保存] をクリックします。Bookshelf アプリにエントリが追加されました。

    Moby Dick の Bookshelf アプリのエントリ
  6. Cloud Console で Firestore ページを更新するには、[更新] をクリックします。データが Firestore に表示されます。Bookshelf アプリは、各書籍を一意の ID とともに Firestore ドキュメントとして保存し、これらのドキュメントはすべて Firestore コレクション内に保存されます。このチュートリアルでは、コレクションを書籍と呼びます。 Firestore ドキュメントの例。

Firestore は Firestore クライアント ライブラリを使用して書籍を保存します。 Firestore ドキュメントを取得する例を次に示します。

public class FirestoreDao implements BookDao {
      private CollectionReference booksCollection;

      public FirestoreDao() {
        Firestore firestore = FirestoreOptions.getDefaultInstance().getService();
        booksCollection = firestore.collection("books");
      }

      private Book documentToBook(DocumentSnapshot document) {
        Map<String, Object> data = document.getData();
        if (data == null) {
          System.out.println("No data in document " + document.getId());
          return null;
        }

        return new Book.Builder()
            .author((String) data.get(Book.AUTHOR))
            .description((String) data.get(Book.DESCRIPTION))
            .publishedDate((String) data.get(Book.PUBLISHED_DATE))
            .imageUrl((String) data.get(Book.IMAGE_URL))
            .createdBy((String) data.get(Book.CREATED_BY))
            .createdById((String) data.get(Book.CREATED_BY_ID))
            .title((String) data.get(Book.TITLE))
            .id(document.getId())
            .build();
      }

      @Override
      public String createBook(Book book) {
        String id = UUID.randomUUID().toString();
        DocumentReference document = booksCollection.document(id);
        Map<String, Object> data = Maps.newHashMap();

        data.put(Book.AUTHOR, book.getAuthor());
        data.put(Book.DESCRIPTION, book.getDescription());
        data.put(Book.PUBLISHED_DATE, book.getPublishedDate());
        data.put(Book.TITLE, book.getTitle());
        data.put(Book.IMAGE_URL, book.getImageUrl());
        data.put(Book.CREATED_BY, book.getCreatedBy());
        data.put(Book.CREATED_BY_ID, book.getCreatedById());
        try {
          document.set(data).get();
        } catch (InterruptedException | ExecutionException e) {
          e.printStackTrace();
        }

        return id;
      }

      @Override
      public Book readBook(String bookId) {
        try {
          DocumentSnapshot document = booksCollection.document(bookId).get().get();

          return documentToBook(document);
        } catch (InterruptedException | ExecutionException e) {
          e.printStackTrace();
        }
        return null;
      }

      @Override
      public void updateBook(Book book) {
        DocumentReference document = booksCollection.document(book.getId());
        Map<String, Object> data = Maps.newHashMap();

        data.put(Book.AUTHOR, book.getAuthor());
        data.put(Book.DESCRIPTION, book.getDescription());
        data.put(Book.PUBLISHED_DATE, book.getPublishedDate());
        data.put(Book.TITLE, book.getTitle());
        data.put(Book.IMAGE_URL, book.getImageUrl());
        data.put(Book.CREATED_BY, book.getCreatedBy());
        data.put(Book.CREATED_BY_ID, book.getCreatedById());
        try {
          document.set(data).get();
        } catch (InterruptedException | ExecutionException e) {
          e.printStackTrace();
        }
      }

      @Override
      public void deleteBook(String bookId) {
        try {
          booksCollection.document(bookId).delete().get();
        } catch (InterruptedException | ExecutionException e) {
          e.printStackTrace();
        }
      }

      private List<Book> documentsToBooks(List<QueryDocumentSnapshot> documents) {
        List<Book> resultBooks = new ArrayList<>();
        for (QueryDocumentSnapshot snapshot : documents) {
          resultBooks.add(documentToBook(snapshot));
        }
        return resultBooks;
      }

      @Override
      public Result<Book> listBooks(String startTitle) {
        Query booksQuery = booksCollection.orderBy("title").limit(10);
        if (startTitle != null) {
          booksQuery = booksQuery.startAfter(startTitle);
        }
        try {
          QuerySnapshot snapshot = booksQuery.get().get();
          List<Book> results = documentsToBooks(snapshot.getDocuments());
          String newCursor = null;
          if (results.size() > 0) {
            newCursor = results.get(results.size() - 1).getTitle();
          }
          return new Result<>(results, newCursor);
        } catch (InterruptedException | ExecutionException e) {
          e.printStackTrace();
        }
        return new Result<>(Lists.newArrayList(), null);
      }

      @Override
      public Result<Book> listBooksByUser(String userId, String startTitle) {
        Query booksQuery =
            booksCollection.orderBy("title").whereEqualTo(Book.CREATED_BY_ID, userId).limit(10);
        if (startTitle != null) {
          booksQuery = booksQuery.startAfter(startTitle);
        }
        try {
          QuerySnapshot snapshot = booksQuery.get().get();
          List<Book> results = documentsToBooks(snapshot.getDocuments());
          String newCursor = null;
          if (results.size() > 0) {
            newCursor = results.get(results.size() - 1).getTitle();
          }
          return new Result<>(results, newCursor);
        } catch (InterruptedException | ExecutionException e) {
          e.printStackTrace();
        }
        return new Result<>(Lists.newArrayList(), null);
      }
    }

Firestore の使用の詳細については、Firestore にデータを追加するをご覧ください。

Cloud Storage にファイルのアップロードを保存する

書籍を追加したので、書籍の表紙の画像を追加します。ファイルをインスタンスに保存することはできません。データベースは画像ファイルに適していません。代わりに、Cloud Storage を使用します。

Cloud Storage は Google Cloud 向けのメインの blob ストアです。Cloud Storage を使用して、Google Cloud で共有したいアプリのアセットをホストできます。Cloud Storage を使用するには、データを保存する基本的なコンテナである Cloud Storage バケットを作成する必要があります。

  1. Cloud Console で、[Cloud Storage ブラウザ] ページに移動します。

    Cloud Storage ブラウザページに移動

  2. [バケットを作成] をクリックします。
  3. [バケットを作成] ダイアログで、Google Cloud のプロジェクト ID を文字列 _bucket に付け加える形でバケットの名前を入力して、名前を YOUR_PROJECT_ID_bucket の形にします。この名前はバケット名の要件を満たしている必要があります。他のすべてのフィールドはデフォルト値のままにします。
  4. [作成] をクリックします。
  5. バケットの作成後、[Edit book] をクリックして、書籍の表紙としてアップロードする画像を選択します。たとえば、このパブリック ドメインの画像を使用できます。
    Moby Dick の書籍の表紙
  6. [保存] をクリックします。Bookshelf アプリのエントリがあるホームページにリダイレクトされます。
    Moby Dick の Bookshelf アプリのエントリ

Bookshelf アプリは、Cloud Storage クライアント ライブラリを使用して、アップロードされたファイルを Cloud Storage に送信します。

public class CloudStorageHelper {

      private final Logger logger = Logger.getLogger(CloudStorageHelper.class.getName());
      private static Storage storage = null;

      static {
        storage = StorageOptions.getDefaultInstance().getService();
      }

      /**
       * Uploads a file to Google Cloud Storage to the bucket specified in the BUCKET_NAME environment
       * variable, appending a timestamp to end of the uploaded filename.
       */
      public String uploadFile(FileItemStream fileStream, final String bucketName)
          throws IOException, ServletException {
        checkFileExtension(fileStream.getName());

        System.out.println("FileStream name: " + fileStream.getName() + "\nBucket name: " + bucketName);

        DateTimeFormatter dtf = DateTimeFormat.forPattern("-YYYY-MM-dd-HHmmssSSS");
        DateTime dt = DateTime.now(DateTimeZone.UTC);
        String dtString = dt.toString(dtf);
        final String fileName = fileStream.getName() + dtString;

        // the inputstream is closed by default, so we don't need to close it here
        @SuppressWarnings("deprecation")
        BlobInfo blobInfo =
            storage.create(
                BlobInfo.newBuilder(bucketName, fileName)
                    // Modify access list to allow all users with link to read file
                    .setAcl(new ArrayList<>(Arrays.asList(Acl.of(User.ofAllUsers(), Role.READER))))
                    .build(),
                fileStream.openStream());
        logger.log(
            Level.INFO, "Uploaded file {0} as {1}", new Object[] {fileStream.getName(), fileName});
        // return the public download link
        return blobInfo.getMediaLink();
      }

      /** Checks that the file extension is supported. */
      private void checkFileExtension(String fileName) throws ServletException {
        if (fileName != null && !fileName.isEmpty() && fileName.contains(".")) {
          String[] allowedExt = {".jpg", ".jpeg", ".png", ".gif"};
          for (String ext : allowedExt) {
            if (fileName.endsWith(ext)) {
              return;
            }
          }
          throw new ServletException("file must be an image");
        }
      }
    }

Cloud Storage の使用の詳細については、入門ガイドのリストをご覧ください。

Google Cloud のオペレーション スイートを使用したアプリのモニタリング

アプリをデプロイし、書籍を作成して修正しました。ユーザーのためにこのようなイベントをモニタリングするには、アプリケーション パフォーマンス管理を使用します。

Cloud Logging でログをモニタリングする

  1. Google Cloud で、ログビューアに移動します。

    ログビューアに移動する

    アプリをリアルタイムでモニタリングできます。アプリに問題が発生した場合は、まずこちらの画面を確認してください。

    Stackdriver ログビューア
  2. [リソース] プルダウン リストで、[Cloud Run Revision, bookshelf] を選択します。

Error Reporting でエラーをモニタリング

  1. Cloud Console で、[Error Reporting] ページに移動します。
    [Error Reporting] ページに移動
    Error Reporting では、アプリ内のエラーと例外が強調表示され、そのアラートを設定できます。
  2. ブラウザで、アプリの /errors URL にアクセスします。
    YOUR_CODE_RUN_URL/errors

    これにより、新しいテストの例外が生成され、Google Cloud のオペレーション スイートに送信されます。

  3. Cloud Console で、[Error Reporting] ページに戻り、しばらくすると新しいエラーが表示されます。[自動再読み込み] をクリックすると、ページを手動で更新する必要がなくなります。

    Error Reporting のエラー メッセージ。

クリーンアップ

このチュートリアルで使用したリソースについて、Google Cloud Platform アカウントに課金されないようにする手順は次のとおりです。

プロジェクトの削除

  1. Cloud Console で [リソースの管理] ページに移動します。

    [リソースの管理] ページに移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

次のステップ