はじめに
この記事は、Open LibertyをRuntimeとしたJava(JAX-RS)とWeb ComponentsとCarbon Design Systemによるマイクロサービス・アプリケーションの実装サンプルの紹介です。
Open LibertyとWeb Componentsによって小さいアプリケーションを作成し、
OpenShift上のオペレータを使用してデプロイし、コンテナとして稼働させるということを経験しました。
その経験の共有のために記事にします。
試したサンプルのアーキテクチャの特徴は、下記です。
- Open LibertyをRuntimeとしたJAX-RSによるサービスの実装
- Web ComponentsによるWeb UI実装。JavaScriptライブラリにLitを使用
- デザイン・システム(Carbon Design)を使用して、見栄えを確保、
このアーキテクチャは、バックエンドは、Javaによる既存資産を流用しつつ、フロント・エンドは、Web Componentsによって、いまどきのデザインを適用したい、というような状況にお勧めです。
また、上記の組み合わせは、Node.jsによるトランスパイルを必要せず、既存のシステムにも容易に統合可能です。
都合上、特定企業に関連した技術が目につきますが、まったく宣伝の意図はございません。
用語紹介
Open Libertyとは
Open Libertyは、OSSのJakarta EE(旧Java EE/J2EE) Runtimeです。
商用バージョンとしてIBM Websphere Libertyがあります。
Libertyのコードは、OSGiによってモジュール化されているため、最小限の機能(プロファイル)での運用が可能という特徴があります。
今回の例では、Eclipse Micro Profileを使用します。
JAX-RSとは
AX-RS(Java API for RESTful Web Services)は、JavaでRESTサービスを実装するためのAPI仕様です。
アノテーションを使用して、Restful WebサービスのURLを定義できます。
以下の例では、GET /api/files
というリクエストで、ファイルのリストを取得し、
DELETE /api/files/filename
というリクエストで、ファイルを削除します。
@Path("/files")
public class FilesController {
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<FileInfo> getFiles() {
return fileService.getFileList();
}
@DELETE
@Path("/{filename}")
@Produces(MediaType.APPLICATION_JSON)
public boolean deleteFile(@PathParam("filename")String filename) {
return fileService.deleteFile(filename);
}
Web Componentsとは
Web Components | MDNによると、Web Components は、再利用可能なカスタム要素を作成し、ウェブアプリの中で利用するための、一連のテクノロジーです。Web Components自体は、HTMLやJavaScript,CSSというテクノロジーの標準の組み合わせのため、すぐに始めることができます。
LITとは
Web Componentsでの実装を支援するJavaScriptのフレームワークです。シンプルかつ軽量であり、React等の他のフレームワークと一緒に使用することができます。Node.jsを使わず、CDNから使用可能です。
デザイン・システムとは
デザイン・システムは、再利用可能なコンポーネントや、コンポーネントに関するガイド・ドキュメント等を含めたセットです。
デザイン・システムを使用することで、一貫したUI経験を提供できます。
例
- Material Design https://material.io/design
- Carbon Design System https://www.carbondesignsystem.com/
当記事の例では、Carbon Design System のWeb Components版を使用しています。Node.jsを使わず、CDNから使用可能です。
#サンプル
サンプル・アプリケーションとして、
特定のディレクトリ上のファイルの一覧を表示し、ダウンロード、および、削除をできるようなアプリケーションを実装します。
##Open Libertyを使用したJAX-RSの実装
Open Libertyをダウンロード
https://openliberty.io/start/ からOpen Libertyをダウンロードします。
適切なディレクトリに展開し、下記コマンドを実行します。このコマンドにより、マイクロ・プロファイルに必要なライブラリがダウンロードされ、Javaのコンパイル、および、Open Libertyが実行されます。
cd hello-app
./mvnw liberty:run
ブラウザを開き、http://localhsot:9080/
にアクセスすると、Open Libetyの画面が表示されます。
src/main/java配下に、パッケージおよび、JAX-RSのクラスを定義します。
package com.example.filelist.rest;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("/api")
public class RestApplication extends Application {
}
FilesController.javaでは、downloadBaseDirという変数をもとに、ファイルを表示するディレクトリを取得します。
downloadBaseDirは、MicroProfile Configの変数として定義されていることを期待しています。
実稼働時には、Open Shiftおよび、Kubernetesから値が渡ります。
package com.example.filelist.rest;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
@Path("/files")
public class FilesController {
private final String MNT_PATH;
public FilesController() {
Config config = ConfigProvider.getConfig();
MNT_PATH = config.getValue("downloadBaseDir", String.class);
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<FileInfo> getFiles() {
return this.getFileList();
}
@GET
@Path("/download/{filename}")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response downloadFile(@PathParam("filename")String filename) {
File file = new File(MNT_PATH,filename);
ResponseBuilder response = Response.ok((Object) file);
response.header("Content-Disposition",
"attachment; filename=\""+filename+"\"");
return response.build();
}
@DELETE
@Path("/{filename}")
@Produces(MediaType.APPLICATION_JSON)
public boolean delete(@PathParam("filename")String filename) {
return this.deleteFile(filename);
}
}
フロントエンドとのデータ通信に使用されるクラス
package com.example.filelist.rest;
import java.io.File;
import java.util.Date;
public class FileInfo {
private String fileName;
private long size;
private long lastModified;
private Date lastModifiedDate;
public long getLastModified() {
return lastModified;
}
public void setLastModified(long lastModified) {
this.lastModified = lastModified;
}
public Date getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(Date lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
public FileInfo(File file) {
this.fileName = file.getName();
this.lastModified = file.lastModified();
this.lastModifiedDate = new Date(file.lastModified());
this.size = file.length();
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
}
##WebComponents/Carbon Design System/Litを使用したフロントエンド実装
この例では、WebComponents/Carbon Design System/Litは、CDNを使用しています。
WebComponents/Carbon Design System/Litの詳しい説明は、省きますが、
これらの仕様・フレームワークを使用することで、容易にコンポーネントベースのWeb UI部品を実装・利用ができます。
import { LitElement, html } from 'https://unpkg.com/lit-element/lit-element.js?module';
import 'https://1.www.s81c.com/common/carbon/web-components/tag/latest/button.min.js';
import 'https://1.www.s81c.com/common/carbon/web-components/tag/latest/data-table.min.js';
import 'https://1.www.s81c.com/common/carbon/web-components/tag/latest/modal.min.js';
import 'https://1.www.s81c.com/common/carbon/web-components/tag/latest/input.min.js';
import 'https://1.www.s81c.com/common/carbon/web-components/tag/latest/form.min.js';
import './my-deleteFileButton.js';
class MyFileList extends LitElement {
static get properties() {
return {
fileLists: { type: Array },
fileName: { type: String }
};
}
constructor() {
super();
this.fileLists = [];
this.fileName = "";
this.addEventListener('fetchFileList', this._fetchFileList);
}
_fetchFileList(){
fetch('./api/files').then(function(resp) {
return resp.json();
}).then((resp) => {
console.log(resp);
this.fileLists = resp;
});
}
firstUpdated() {
this._fetchFileList();
}
humanFileSize(bytes, dp=1) {
const thresh = 1024;
if (Math.abs(bytes) < thresh) {
return bytes + ' B';
}
const units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] ;
let u = -1;
const r = 10**dp;
do {
bytes /= thresh;
++u;
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
return bytes.toFixed(dp) + ' ' + units[u];
}
formatDate(longDateTime) {
let date = new Date();
date.setTime(longDateTime);
return date.toLocaleString();
}
render() {
const { fileLists } = this;
const linkpath = "./api/files/download/";
return html`
<bx-data-table>
<bx-table>
<bx-table-head>
<bx-table-header-row>
<bx-table-header-cell>File
Name</bx-table-header-cell>
<bx-table-header-cell>Size</bx-table-header-cell>
<bx-table-header-cell>Last
Modified</bx-table-header-cell>
<bx-table-header-cell>Option</bx-table-header-cell>
</bx-table-header-row>
</bx-table-head>
<bx-table-body>
${fileLists.map((item) => html`
<bx-table-row>
<bx-table-cell><a href=${linkpath}${item.fileName}>${item.fileName}</a></bx-table-cell>
<bx-table-cell>${this.humanFileSize(item.size)}</bx-table-cell>
<bx-table-cell>${this.formatDate(item.lastModified)}</bx-table-cell>
<bx-table-cell>
<my-delfilebtn fileName="${item.fileName}"></my-delfilebtn>
</bx-table-cell>
</bx-table-row>
`)}
</bx-table-body>
</bx-table>
</bx-data-table>
`;
}
}
customElements.define('my-filelist', MyFileList);
まとめ
この記事では、以下についての簡単な実装例を紹介致しました。
- Open LibertyをRuntimeとしたJAX-RSによるサービスの実装
- Web ComponentsによるWeb UI実装。JavaScriptライブラリにLitを使用
- デザイン・システム(Carbon Design)を使用して、見栄えを確保
繰り返しとなりますが、このアーキテクチャは、バックエンドは、Javaによる既存資産を流用しつつ、フロント・エンドは、Web Componentsによって、いまどきのデザインを適用したい、というような状況にお勧めです。
当記事が誰かの助けになれば幸いです。