CRUDのWebApi開発は不要！JsonでWebApiを作れるシステム「versatileapi」を作った話

# はじめに
タイトルの通りです
定義を設定するだけで、CRUDのRESTWebapiを作れるシステムです

似たようなもので、FirebaseのRealtimeDatabaseやFireStoreがありますが
色々と制限が多く、バリデーションが難しいようだったので作ってみました

Java(Spring Boot)とMongoDBで作りました

ここで使ったシステムです
[エンジニア・プログラマにしか使えないSNSを作ってみた話](https://qiita.com/HawkClaws/items/599d7666f55e79ef7f56)

`2020/07/20 ソースコード公開`
https://github.com/HawkClaws/versatileapi

# フロー図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911989/c9d9c3f3-5f05-30bc-cb2f-9c8204f82df6.png)



# フロー詳細

## WebApi定義を登録

管理者用WebApiを使用し、API定義を登録します


登録例
`apiUrl:APIのURL`
`apiSecret:認証に使用するシークレットキー`
`jsonSchema:バリデーションに使用するJsonSchema`
`methodSettings:各メソッドの設定`


```number-battle.json
{
	"apiSecret": "",
	"apiUrl":"number_battle",
	"jsonSchema": {
        "additionalProperties":false,
		"type": "object",
		"properties": {
			"name": {
                "description": "名前",
				"type": "string",
                "maxLength": 30
			},
            "secondNumber": {
                "description": "２番目に大きいと予想した数字",
				"type": "number"
			}
		},
		"required": [
			"name",
			"secondNumber"
		]
	},
	"methodSettings": [
		{
			"httpMethod": "GET",
			"behavior": "Allow"
		},
        {
			"httpMethod": "POST",
			"behavior": "Allow"
		},
        {
			"httpMethod": "PUT",
			"behavior": "NotImplemented"
		},
        {
			"httpMethod": "DELETE",
			"behavior": "NotImplemented"
		}
	]
}
```


## WebApiを実行
１．URIからリソース・API定義・リソースIDを特定
２．WebApi定義を取得（もちろんOnMemoryキャッシュする）
３．定義を使用してJsonSchemaバリデーション・認証・許可されているメソッドをチェック
４．各メソッドのAPIを実行

ソースコード

```VersatileController.java

	@RequestMapping(value = "/api/**")
	public ResponseEntity<Object> versatileApi(HttpServletRequest request)
			throws IOException, InterruptedException, ExecutionException {
		
		//URLからリソース（DBテーブル）やリソースのID特定
		RepositoryUrlInfo info = urlConverter.getRepositoryInfo(request.getRequestURI());
		String method = request.getMethod();
		String body = request.getReader().lines().collect(Collectors.joining("\r\n"));

		writeLog(request, body);

		ResponseEntity responseEntity = null;
		try {
			//JsonSchemaバリデーション・認証・許可されているメソッドをチェック
			responseEntity = versatileService.checkUseApi(info.repositoryKey, info.id, method, body,
					request.getHeader(ConstData.AUTHORIZATION));
		} catch (JsonParsingException e) {
			return new ResponseEntity<>(gson.toJson("JsonParseError:" + e.getMessage()), new HttpHeaders(),
					HttpStatus.BAD_REQUEST);
		}

		if (responseEntity != null)
			return responseEntity;

		Object response = null;
		//各メソッドの処理を実行
		switch (method) {
		case HttpMethods.GET:
			response = versatileService.get(info.id, info.repositoryKey, request.getQueryString());
			if (response == null || Objects.equal(response, "")) {
				return new ResponseEntity<>(response, new HttpHeaders(), HttpStatus.NOT_FOUND);
			} else {
				return new ResponseEntity<>(response, new HttpHeaders(), HttpStatus.OK);
			}

		case HttpMethods.POST:
			response = versatileService.post(info.id, info.repositoryKey, request.getQueryString(), body,
					request.getRemoteAddr());
			return new ResponseEntity<>(response, new HttpHeaders(), HttpStatus.CREATED);
			
		case HttpMethods.PUT:
			response = versatileService.put(info.id, info.repositoryKey, request.getQueryString(), body,
					request.getRemoteAddr());
			return new ResponseEntity<>(response, new HttpHeaders(), HttpStatus.OK);
			
		case HttpMethods.DELETE:
			response = versatileService.delete(info.id, info.repositoryKey, request.getQueryString());
			return new ResponseEntity<>(response, new HttpHeaders(), HttpStatus.NO_CONTENT);

		}
		return new ResponseEntity<>("", new HttpHeaders(), HttpStatus.NOT_IMPLEMENTED);
	}
```

以上、このような感じです


# おまけ

投稿者の中で２番目に大きい数値を当てるゲーム
一番大きい数値を予想して、それより小さい数字、他の数字より大きい数字を当てる
心理的？？ゲームです。いや、運ゲーかも

####エンドポイント
`https://versatileapi.herokuapp.com/api`

数値を当てる
`POST　/number_battle`

結果を見る
`GET　/number_battle/all?$orderby=secondNumber desc&$skip=1&$limit=1`