Open Libertyを使用して、JAX-RSとWeb componentsとCarbon Designで、モダンなUIとマイクロ・サービス・アプリケーションを簡単に実装する

# はじめに
この記事は、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](https://openliberty.io/)は、OSSのJakarta EE(旧Java EE/J2EE) Runtimeです。
商用バージョンとしてIBM Websphere Libertyがあります。
Libertyのコードは、OSGiによってモジュール化されているため、最小限の機能（プロファイル）での運用が可能という特徴があります。
今回の例では、Eclipse Micro Profileを使用します。

https://openliberty.io/

## JAX-RSとは
AX-RS（Java API for RESTful Web Services）は、JavaでRESTサービスを実装するためのAPI仕様です。
アノテーションを使用して、Restful WebサービスのURLを定義できます。

以下の例では、`GET /api/files` というリクエストで、ファイルのリストを取得し、
`DELETE /api/files/filename`というリクエストで、ファイルを削除します。

```java:JAX-RSのサンプル
@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](https://developer.mozilla.org/ja/docs/Web/Web_Components)によると、Web Components は、再利用可能なカスタム要素を作成し、ウェブアプリの中で利用するための、一連のテクノロジーです。Web Components自体は、HTMLやJavaScript,CSSというテクノロジーの標準の組み合わせのため、すぐに始めることができます。

https://developer.mozilla.org/ja/docs/Web/Web_Components

## LITとは
Web Componentsでの実装を支援するJavaScriptのフレームワークです。シンプルかつ軽量であり、React等の他のフレームワークと一緒に使用することができます。Node.jsを使わず、CDNから使用可能です。


https://lit.dev/


## デザイン・システムとは
デザイン・システムは、再利用可能なコンポーネントや、コンポーネントに関するガイド・ドキュメント等を含めたセットです。
デザイン・システムを使用することで、一貫したUI経験を提供できます。

例
- Material Design https://material.io/design
- Carbon Design System https://www.carbondesignsystem.com/

当記事の例では、Carbon Design System のWeb Components版を使用しています。Node.jsを使わず、CDNから使用可能です。

https://www.carbondesignsystem.com/developing/frameworks/web-components/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203516/e4cf35db-5647-7781-9f04-9dbae52af9ea.png)


#サンプル
サンプル・アプリケーションとして、
特定のディレクトリ上のファイルの一覧を表示し、ダウンロード、および、削除をできるようなアプリケーションを実装します。


##Open Libertyを使用したJAX-RSの実装
### Open Libertyをダウンロード
https://openliberty.io/start/ からOpen Libertyをダウンロードします。

例として、以下のような設定とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203516/678a3184-54cb-21fd-0bd3-73cf9b9e1d13.png)


適切なディレクトリに展開し、下記コマンドを実行します。このコマンドにより、マイクロ・プロファイルに必要なライブラリがダウンロードされ、Javaのコンパイル、および、Open Libertyが実行されます。

```
cd hello-app
./mvnw liberty:run
```

ブラウザを開き、`http://localhsot:9080/`にアクセスすると、Open Libetyの画面が表示されます。

src/main/java配下に、パッケージおよび、JAX-RSのクラスを定義します。

```java:RestApplication.java
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から値が渡ります。

https://www.eclipse.org/community/eclipse_newsletter/2017/september/article3.php

```java:FilesController.java
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);
    }
}
```
フロントエンドとのデータ通信に使用されるクラス

```java:FileInfo.java
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部品を実装・利用ができます。

```javascript
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);
```

画面の例です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203516/561da67e-1a8a-9829-e64a-ba411b4865b9.png)

# まとめ
この記事では、以下についての簡単な実装例を紹介致しました。
- Open LibertyをRuntimeとしたJAX-RSによるサービスの実装
- Web ComponentsによるWeb UI実装。JavaScriptライブラリにLitを使用
- デザイン・システム（Carbon Design）を使用して、見栄えを確保

繰り返しとなりますが、このアーキテクチャは、バックエンドは、Javaによる既存資産を流用しつつ、フロント・エンドは、Web Componentsによって、いまどきのデザインを適用したい、というような状況にお勧めです。

当記事が誰かの助けになれば幸いです。