GAE / Java8 trial (Part 4: "Datastore access logic")

theme

Last time, I touched on the test code that can be done when Maven automatically generates a Java 8 application for GAE. This time, I will try to add Datastore access logic with test first in mind.

GAE Trial Index

-GAE / Java8 trial (No. 0: "About App Engine") -GAE / Java8 trial (Part 1: "Create and deploy a web application with Java8") -GAE / Java8 trial (Part 2: "Java application explanation") -GAE / Java8 trial (Part 3: "Java application test code explanation")

Development environment

OS

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="17.10 (Artful Aardvark)"

Java

$ java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

IDE

Everyone loves IntelliJ IDEA

reference

--Create tests for datastore and Memcache --Cloud Datastore Overview -Entity Property Reference

Practice

■ Design

Determine the specifications of the functions to be implemented. The theme is the "book list management" system, which is often found in GCP tutorials. As a function to try the access logic to Datastore this time, After POSTing the "book name", it shall be registered in the Datastore.

■ Test code

Write the following test code according to the specifications. Manipulate the mock request so that the "book title" can be obtained from the request parameters. Then, execute doPost (~~) to verify that the above "book name" is registered in the book kind.

  @Test
public void POSTing the book name will be registered in the Datastore() throws EntityNotFoundException, ServletException, IOException {
    /*
     * SetUp
     */
    Map<String, String[]> parameterMap = new HashMap<>();
    parameterMap.put("bookName", new String[]{"Microservices architecture"});
    when(mockRequest.getParameterMap()).thenReturn(parameterMap);

    /*
     * Execute
     */
    servletUnderTest.doPost(mockRequest, mockResponse);

    /*
     * Assert
     */
    DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
    Entity e = ds.get(KeyFactory.createKey("book", 1));
    String microService = (String)e.getProperty("bookName");
    assertThat(microService).isEqualTo("Microservices architecture");
  }

https://github.com/sky0621/java-webapi-for-gae-study/blob/master/sky0621/src/test/java/com/example/sky0621/HelloAppEngineTest.java

By the way, since it is test first, the above test code is a compile error without doPost (~~). After that, if you write only the definition of doPost (~~) and execute the test code, it will be as follows. The logic to register the "book name" passed as a request parameter in the Datastore is not implemented, so it is a natural result. In test first, first decide the specifications and write the test code to confirm them. (At that point, it won't compile, but it's important to start from there) Fail the test first. I know it will fail, but I dare to fail. Since it is before logic implementation, the test execution result is NG. By confirming this first, it makes sense that the test execution result is OK after the logic is implemented. (If you do not confirm that it fails first, the correct verification code may not have been written, the test execution result may have been OK from the beginning, and it may become test code that has not been tested that it has been implemented correctly. Because there is)


com.google.appengine.api.datastore.EntityNotFoundException: No entity was found matching the key: book("micro")

	at com.google.appengine.api.datastore.BaseAsyncDatastoreServiceImpl$1.wrap(BaseAsyncDatastoreServiceImpl.java:174)
	at com.google.appengine.api.datastore.BaseAsyncDatastoreServiceImpl$1.wrap(BaseAsyncDatastoreServiceImpl.java:169)
	at com.google.appengine.api.utils.FutureWrapper.wrapAndCache(FutureWrapper.java:56)
	at com.google.appengine.api.utils.FutureWrapper.get(FutureWrapper.java:93)
	at com.google.appengine.api.datastore.FutureHelper.getInternal(FutureHelper.java:76)
	at com.google.appengine.api.datastore.FutureHelper.quietGet(FutureHelper.java:63)
	at com.google.appengine.api.datastore.DatastoreServiceImpl.get(DatastoreServiceImpl.java:41)
	at com.example.sky0621.HelloAppEngineTest.When you POST the book name, it will be registered in the Datastore.(HelloAppEngineTest.java:100)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

10 23, 2018 12:21:13 am com.google.appengine.api.datastore.dev.LocalDatastoreService cleanupActiveServices
information: scheduler shutting down.
Disconnected from the target VM, address: '127.0.0.1:37713', transport: 'socket'

Process finished with exit code 255

Now that the test failed as expected, it's finally time to implement the Datastore registration logic.

■ Implementation

Like this. It is not a level that can be used with the product code, but for the time being, it will be a code that captures the "book name" from the request parameter and registers it in Datastoreni.


  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    DatastoreService ds = DatastoreServiceFactory.getDatastoreService();

    req.getParameterMap().forEach((k, v) -> {
      Key key = KeyFactory.createKey("book", 1);
      Entity e = new Entity(key);
      e.setProperty("bookName", Arrays.stream(v).collect(Collectors.joining()));
      ds.put(e);
    });
  }

https://github.com/sky0621/java-webapi-for-gae-study/blob/master/sky0621/src/main/java/com/example/sky0621/HelloAppEngine.java

If you run the test code after this implementation, the test result will be OK this time.

Summary

If you do it test-first, it will take some time to implement even a small function. However, "reasonable time" includes specification review, implementation, unit testing, refactoring, rework reduction cost, etc., so it is really good for cost performance.

Recommended Posts

GAE / Java8 trial (Part 4: "Datastore access logic")
GAE / Java8 trial (Part 6: "Deployment failure")
GAE / Java8 trial (Part 5: "Console for local development")
Access modifier [Java]
java practice part 1
Access API.AI from Java
About Java access modifiers
Studying Java ~ Part 8 ~ Cast