Quantcast
Channel: Oracle Blogs 日本語のまとめ
Viewing all articles
Browse latest Browse all 760

[Microservices]Hibernateを使ったHelidonマイクロサービスの作成とデプロイ パート2/ Building And Deploying A Helidon Microservice With Hibernate Part 2

$
0
0
原文はこちら

このシリーズではここまで、KubernetesおよびDocker用のクラウド環境のセットアップAutonomous DBの構築と稼働HelidonとHibernateを使うマイクロサービスの作成を行ってきました。ここまでで、われわれの最初のマイクロサービスである架空のソーシャルメディアアプリケーションについて、ユーザーデータを永続化するためのデータモデルと永続化ロジックの作成に進む準備ができました。もしこれまでのシリーズを読んでいない場合には、これからご説明することについていきやすくなるように、以前のポストを先に読んでおくことをオススメします。

注意:このシリーズのすべてのコードはGitHubに置いてあります。
ユーザーマイクロサービスアプリケーションの設定などはここまででできたため、ユーザーオブジェクトを表すモデルを作成していきましょう。 model という新しいパッケージを作成して、その中に User.javaクラスを作成します。データベースカラムにマップするためのいくつかのプロパティと、永続化を試みる前にデータが適正であることを確かにしておくためのいくつかのバリデーション制約を追加します。覚えているかもしれませんが、われわれのテーブルは以下のDDLスクリプトで作成されたものでした:
CREATETABLEusers(
"ID"VARCHAR2(32 BYTE) DEFAULT ONNULL SYS_GUID(), 
"FIRST_NAME"VARCHAR2(50 BYTE) COLLATE "USING_NLS_COMP"NOT NULL ENABLE, 
"LAST_NAME"VARCHAR2(50 BYTE) COLLATE "USING_NLS_COMP"NOT NULL ENABLE, 
"USERNAME"VARCHAR2(50 BYTE) COLLATE "USING_NLS_COMP"NOT NULL ENABLE, 
"CREATED_ON"TIMESTAMP (6) DEFAULT ONNULLCURRENT_TIMESTAMP
CONSTRAINT"USER_PK"PRIMARY KEY ("ID")
);
view rawcreate-user-tbl.sql hosted with ❤ by GitHub
なので、 User オブジェクトには5つのプロパティが必要です。idfirstNamelastNameusername と createdOnです。firstNamelastNameusername のプロパティはNull不可のstringで、最大50文字にします。 ID プロパティはGUIDで、 createdOn プロパティはタイムスタンプです。そういうわけで、われわれのUserオブジェクトのプロパティとバリデーションアノテーションは以下のようになります:
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY, generator="system-uuid")
@GenericGenerator(name="system-uuid", strategy="guid")
@Column(name="id", unique=true, nullable=false)
privateString id;
@Column(name="first_name")
@NotNull
@Size(max=50)
privateString firstName;
@Column(name="last_name")
@NotNull
@Size(max=50)
privateString lastName;
@Column(name="username")
@NotNull
@Size(max=50)
privateString username;
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
@Column(name="created_on")
privateDate createdOn =newDate();
view rawUser.java hosted with ❤ by GitHub
Userオブジェクトの残りの部分はふつうのテンプレで、なにも難しいところはありません。
次のステップはサービス永続化オペレーションのためのリポジトリ作成です。作成した user パッケージの中に UserRepository.java というクラスを作りましょう。
@RequestScoped
publicclassUserRepository {
}
view rawUserRepository.java hosted with ❤ by GitHub
シリーズの前回のポストで作成したUserProviderをインジェクションし設定情報をこのリポジトリに入れ込むとともにコンストラクタでエンディティマネージャを作成しておきます:
@RequestScoped
publicclassUserRepository {
    @PersistenceContext
    privatestaticEntityManager entityManager;
    @Inject
    publicUserRepository(UserProvideruserProvider) {
        Map<String, Object> configOverrides =newHashMap<String, Object>();
        configOverrides.put("hibernate.connection.url", userProvider.getDbUrl());
        configOverrides.put("hibernate.connection.username", userProvider.getDbUser());
        configOverrides.put("hibernate.connection.password", userProvider.getDbPassword());
        EntityManagerFactory emf =Persistence.createEntityManagerFactory("UserPU", configOverrides);
        entityManager = emf.createEntityManager();
    }
}
view rawUserRepository.java hosted with ❤ by GitHub
ここで validate() メソッドを追加し、Userをセーブする前に適切であることを確かにしておきましょう:
publicSet<ConstraintViolation<User>> validate(User user) {
    Validator validator =Validation.buildDefaultValidatorFactory().getValidator();
    Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
    return constraintViolations;
}
view rawUserRepository.java hosted with ❤ by GitHub
最後に、 save()get()findAll()count() と deleteById()メソッドを追加してリポジトリは完成です。これらはよくあるふつうのCRUDメソッドなので、説明は省いてコードを貼りますね:
publicUser save(User user) {
    entityManager.getTransaction().begin();
    entityManager.persist(user);
    entityManager.getTransaction().commit();
    return user;
}
publicUser get(String id) {
    User user = entityManager.find(User.class, id);
    return user;
}
publicList<User> findAll() {
    return entityManager.createQuery("from User").getResultList();
}
publicList<User> findAll(int offset, int max) {
    Query query = entityManager.createQuery("from User");
    query.setFirstResult(offset);
    query.setMaxResults(max);
    return query.getResultList();
}
publiclong count() {
    Query queryTotal = entityManager.createQuery("Select count(u.id) from User u");
    long countResult = (long)queryTotal.getSingleResult();
    return countResult;
}
publicvoid deleteById(String id) {
    // Retrieve the movie with this ID
    User user = get(id);
    if (user !=null) {
        try {
            entityManager.getTransaction().begin();
            entityManager.remove(user);
            entityManager.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
view rawUserRepository.java hosted with ❤ by GitHub
次に、 GreetResource を修正するか置き換えるかして UserResourceを作成します。このリソースファイルはHelidonでのサービスエンドポイントとして定義したところに格納されています:
@Path("/user")
@RequestScoped
publicclassUserResource {
}
view rawUserResource.java hosted with ❤ by GitHub
これはHelidonに対して /users のパスでクラスの全てのメソッドをリッスンするように命じています。あとでそれぞれのメソッドに対してパスを定義していきますが、その前に @Inject を使って UserRepositoryを取得するコンストラクタを追加しましょう:
@Inject
public UserResource(UserRepository userRepository) {
    this.userRepository = userRepository;
}
view rawUserResource.java hosted with ❤ by GitHub
ではリソースにパスを追加していきます。デフォルトパスを定義するには、 @Path アノテーションをそのメソッドから省きましょう。 http://localhost:8080/user が呼ばれると常にこのメソッドが呼ばれることになります:
@GET
@Produces(MediaType.APPLICATION_JSON)
publicResponse getDefaultMessage() {
    returnResponse.ok(Map.of("OK", true)).build();
}
view rawUserResource.java hosted with ❤ by GitHub
ということで、各CRUDオペレーションを定義するのはリソースメソッドを作成し、適切なリポジトリメソッドを呼び出すということになります。
UserをIDで取得するには:
@Path("/{id}")
@GET
@Produces(MediaType.APPLICATION_JSON)
publicResponse getById(@PathParam("id") String id) {
    User user = userRepository.get(id);
    if( user !=null ) {
        returnResponse.ok(user).build();
    }
    else {
        returnResponse.status(404).build();
    }
}
view rawUserResource.java hosted with ❤ by GitHub
Userをリストするには:
@Path("/list")
@GET
@Produces(MediaType.APPLICATION_JSON)
publicResponse getAllUsers() {
    returnResponse.ok(this.userRepository.findAll()).build();
}
view rawUserRepository.java hosted with ❤ by GitHub
Userのリストをページごとに取り出すには:
@Path("/list/{offset}/{max}")
@GET
@Produces(MediaType.APPLICATION_JSON)
publicResponse getAllUsersPaginated(@PathParam("offset") int offset, @PathParam("max") int max) {
    returnResponse.ok(this.userRepository.findAll(offset, max)).build();
}
view rawUserRepository.java hosted with ❤ by GitHub
ID指定でUserを削除するには:
@Path("/list/{offset}/{max}")
@GET
@Produces(MediaType.APPLICATION_JSON)
publicResponse getAllUsersPaginated(@PathParam("offset") int offset, @PathParam("max") int max) {
    returnResponse.ok(this.userRepository.findAll(offset, max)).build();
}
view rawUserRepository.java hosted with ❤ by GitHub
そして最後に、Userの保存です(保存前に validate() を呼び出し、エラーがあれば422 Unprocessable Entityステータスを返却していることに留意されたし):
@Path("/save")
@POST
@Produces(MediaType.APPLICATION_JSON)
publicResponse saveUser(User user) {
    Set<ConstraintViolation<User>> violations = userRepository.validate(user);
    if( violations.size() ==0 ) {
        userRepository.save(user);
        returnResponse.created(
                uriInfo.getBaseUriBuilder()
                        .path("/user/{id}")
                        .build(user.getId())
        ).build();
    }
    else {
        List<HashMap<String, String>> errors =newArrayList<>();
        violations.stream()
                .forEach( (violation) -> {
                            Object invalidValue = violation.getInvalidValue();
                            HashMap<String, String> errorMap =newHashMap<>();
                            errorMap.put("field", violation.getPropertyPath().toString());
                            errorMap.put("message", violation.getMessage());
                            errorMap.put("currentValue", invalidValue ==null?null: invalidValue.toString());
                            errors.add(errorMap);
                        }
                );
        returnResponse.status(422)
                .entity(Map.of( "validationErrors", errors ))
                .build();
    }
}
view rawUserResource.java hosted with ❤ by GitHub
これでエンドポイントのコンパイルとテストの準備が完了です。 mvn package でサービスをコンパイルし、以下のコマンドでアプリケーションを実行しましょう。いくつかのプロパティにはパスを指定してやる必要があるので、思い出せなければ以前のポストなどをみてWalletファイルへのパスやスキーマユーザー名とパスワードなどを調べてください。パスとクレデンシャルは適切に置き換えて使ってください:
java 
    -Doracle.net.wallet_location=/path/to/wallet \
    -Doracle.net.authentication_services="(TCPS)" \
    -Doracle.net.tns_admin=/wallet-demodb \
    -Djavax.net.ssl.trustStore=/path/to/wallet/cwallet.sso \
    -Djavax.net.ssl.trustStoreType=SSO \
    -Djavax.net.ssl.keyStore=/path/to/wallet/cwallet.sso \
    -Djavax.net.ssl.keyStoreType=SSO \
    -Doracle.net.ssl_server_dn_match=true \
    -Doracle.net.ssl_version="1.2" \
    -Ddatasource.username=[username] \
    -Ddatasource.password=[password] \
    -Ddatasource.url=jdbc:oracle:thin:@demodb_LOW?TNS_ADMIN=/path/to/wallet \
-jar target/user-svc.jar
アプリケーションが起動し、ローカルホストのポート8080番で稼働しているはずです。この時点でエンドポイントのテストを以下のように行なえます:
ユーザーサービスエンドポイントのGET(200 OKが返却):
curl -iX GET http://localhost:8080/user                                                                                                                                                    
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 20 Jun 2019 10:35:06 -0400
transfer-encoding: chunked
connection: keep-alive
{"OK":true} 
view rawtest-path.sh hosted with ❤ by GitHub
新規Userの保存(LocationヘッダでIDが返却される):
curl -iX POST -H "Content-Type: application/json" -d '{"firstName": "Todd", "lastName": "Sharp", "username": "recursivecodes"}' http://localhost:8080/user/save                            
HTTP/1.1 201 Created
Date: Thu, 20 Jun 2019 10:45:38 -0400
Location: http://[0:0:0:0:0:0:0:1]:8080/user/8BC3669097C9EC53E0532110000A6E11
transfer-encoding: chunked
connection: keep-alive
view rawsave-user.sh hosted with ❤ by GitHub
新規Userを不正なデータで保存(422とバリデーションエラーが返却):
curl -iX POST -H "Content-Type: application/json" -d '{"firstName": "A Really Long First Name That Will Be Longer Than 50 Chars", "lastName": null, "username": null}' http://localhost:8080/user/save
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
Date: Mon, 1 Jul 2019 11:21:57 -0400
transfer-encoding: chunked
connection: keep-alive
{"validationErrors":[{"field":"username","message":"may not be null","currentValue":null},{"field":"lastName","message":"may not be null","currentValue":null},{"field":"firstName","message":"size must be between 0 and 50","currentValue":"A Really Long First Name That Will Be Longer Than 50 Chars"}]}
作成したUserのGET:
curl -iX GET http://localhost:8080/user/8BC3669097C9EC53E0532110000A6E11                                                                                                                   
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 20 Jun 2019 10:46:17 -0400
transfer-encoding: chunked
connection: keep-alive
{"id":"8BC3669097C9EC53E0532110000A6E11","firstName":"Todd","lastName":"Sharp","username":"recursivecodes","createdOn":"2019-06-20T14:45:38.509Z"}
view rawget-user.sh hosted with ❤ by GitHub
すべてのUserのList:
curl -iX GET http://localhost:8080/user/list                                                                                                                                               
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 20 Jun 2019 10:46:51 -0400
transfer-encoding: chunked
connection: keep-alive
[{"id":"8BC3669097C9EC53E0532110000A6E11","firstName":"Todd","lastName":"Sharp","username":"recursivecodes","createdOn":"2019-06-20T14:45:38.509Z"}]
view rawlist-users.sh hosted with ❤ by GitHub
UserのDELETE:
curl -iX DELETE http://localhost:8080/user/8BC3669097C9EC53E0532110000A6E11                                                                                                                
HTTP/1.1 204 No Content
Date: Thu, 20 Jun 2019 10:47:21 -0400
connection: keep-alive
view rawdelete-user.sh hosted with ❤ by GitHub
DELETEの確認(ID指定でGETすると404が返却):
curl -iX GET http://localhost:8080/user/8BC3669097C9EC53E0532110000A6E11                                                                                                                   
HTTP/1.1 404 Not Found
Date: Thu, 20 Jun 2019 10:47:43 -0400
transfer-encoding: chunked
connection: keep-alive
view rawconfirm-delete.sh hosted with ❤ by GitHub

これであなたは最初のHelidonとHibernateを使ったマイクロサービスを作成できました!次のポストではこのサービスをDockerおよびKubernetes上にデプロイします。

Viewing all articles
Browse latest Browse all 760

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>