You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by ni...@apache.org on 2018/09/07 14:25:03 UTC

[incubator-servicecomb-saga] 01/02: SCB-820 Updated the inventory example

This is an automated email from the ASF dual-hosted git repository.

ningjiang pushed a commit to branch SCB-820
in repository https://gitbox.apache.org/repos/asf/incubator-servicecomb-saga.git

commit a7f7ad719de67cb3864d8cdb9dfec4b06c77d6a3
Author: Willem Jiang <ji...@huawei.com>
AuthorDate: Fri Sep 7 20:29:02 2018 +0800

    SCB-820 Updated the inventory example
---
 saga-demo/tcc-spring-demo/inventory/pom.xml        |  13 ++-
 .../saga/demo/pack/inventory/InventoryService.java |  73 ++++++++-----
 .../saga/demo/pack/inventory/Product.java          |   5 +
 .../demo/pack/inventory/ProductController.java     |  89 ++++++---------
 .../saga/demo/pack/inventory/ProductDao.java       |   5 +
 .../saga/demo/pack/inventory/ProductOrder.java     |  82 ++++++++++++++
 .../pack/inventory/TccInventoryApplication.java    |   7 +-
 .../inventory/src/main/resources/application.yaml  |  17 ++-
 .../demo/pack/inventory/InventoryServiceTest.java  | 119 +++++++++++++++++++++
 .../demo/pack/inventory/ProductControllerTest.java |  77 +++++++++++++
 .../saga/demo/pack/inventory/TestApplication.java} |   8 +-
 .../saga/demo/pack/ordering/OrderService.java      |   4 +
 .../saga/demo/pack/payment/Payment.java            |   4 +
 13 files changed, 399 insertions(+), 104 deletions(-)

diff --git a/saga-demo/tcc-spring-demo/inventory/pom.xml b/saga-demo/tcc-spring-demo/inventory/pom.xml
index 25f90d6..6c2a218 100644
--- a/saga-demo/tcc-spring-demo/inventory/pom.xml
+++ b/saga-demo/tcc-spring-demo/inventory/pom.xml
@@ -43,10 +43,6 @@
       <artifactId>omega-spring-starter</artifactId>
     </dependency>
     <dependency>
-      <groupId>org.apache.servicecomb.saga</groupId>
-      <artifactId>omega-transport-resttemplate</artifactId>
-    </dependency>
-    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</artifactId>
     </dependency>
@@ -58,6 +54,15 @@
       <groupId>org.assertj</groupId>
       <artifactId>assertj-core</artifactId>
     </dependency>
+    <dependency>
+      <groupId>javax.persistence</groupId>
+      <artifactId>javax.persistence-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.hamcrest</groupId>
+      <artifactId>hamcrest-all</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/InventoryService.java b/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/InventoryService.java
index f5c36d7..37e97c8 100644
--- a/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/InventoryService.java
+++ b/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/InventoryService.java
@@ -16,7 +16,13 @@
  */
 package org.apache.servicecomb.saga.demo.pack.inventory;
 
+import java.util.Collection;
+import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.transaction.Transactional;
+
 import org.apache.servicecomb.saga.omega.transaction.annotations.Participate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -24,45 +30,54 @@ import org.springframework.stereotype.Service;
 @Service
 public class InventoryService {
 
+  private Map<Integer, ProductOrder> orders = new ConcurrentHashMap<>();
+  @Autowired
   private ProductDao productDao;
 
-  /**
-   * Find the product with specific product Id.
-   *
-   * @param productId Product ID
-   * @param requiredCount Required product count
-   * @return return the reserved count, 0 if nothing is available. returns negative value if
-   * insufficient.
-   */
   @Participate(confirmMethod = "confirm", cancelMethod = "cancel")
-  public Integer reserve(Long productId, int requiredCount) {
-    Product product = productDao.findOne(productId);
-    if (Objects.isNull(product)) {
-      throw new IllegalArgumentException("Product not exists at all");
-    }
-
-    // if it is sufficient
-    if (product.getInStock() > requiredCount) {
-      product.setInStock(product.getInStock() - requiredCount);
-      productDao.save(product);
-      return requiredCount;
+  @Transactional
+  public void reserve(ProductOrder order) {
+    Product product = getProduct(order.getProductName());
+    if (product.getInStock() > order.getAmount()) {
+      product.setInStock(product.getInStock() - order.getAmount());
+      productDao.saveAndFlush(product);
+      orders.put(order.getId(), order);
     } else {
-      return product.getInStock() - requiredCount;
+      throw new IllegalArgumentException("The Product is out of stock!");
     }
   }
 
-  public void confirm(Long productId, int requiredCount) {
-    // empty body
+  public void confirm(ProductOrder order) {
+    order.setConfirmed(true);
   }
 
-  public void cancel(Long productId, int requiredCount) {
-    Product product = productDao.findOne(productId);
-    product.setInStock(product.getInStock() + requiredCount);
-    productDao.save(product);
+  @Transactional
+  public void cancel(ProductOrder order) {
+    Product product = productDao.findProduceByName(order.getProductName());
+    product.setInStock(product.getInStock() + order.getAmount());
+    productDao.saveAndFlush(product);
+    order.setCancelled(true);
   }
 
-  @Autowired
-  public void setProductDao(ProductDao productDao) {
-    this.productDao = productDao;
+  @Transactional
+  private Product getProduct(String productName) {
+    Product product = productDao.findProduceByName(productName);
+    if (Objects.isNull(product)) {
+      throw new IllegalArgumentException("Product not exists at all!");
+    }
+    return product;
+  }
+  
+  Integer getInventory(String productName) {
+    Product product = getProduct(productName);
+    return product.getInStock();
+  }
+
+  Collection<ProductOrder> getAllOrders() {
+    return orders.values();
+  }
+
+  void clearAllOrders() {
+    orders.clear();
   }
 }
diff --git a/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/Product.java b/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/Product.java
index 9ddc7e4..d8804f5 100644
--- a/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/Product.java
+++ b/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/Product.java
@@ -22,6 +22,8 @@ import javax.persistence.GeneratedValue;
 import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.Table;
+import javax.persistence.Version;
+
 
 @Entity
 @Table(name = "t_product")
@@ -38,6 +40,9 @@ public class Product implements Serializable {
    */
   private Integer inStock;
 
+  @Version
+  private Integer version;
+
   Product() {
   }
 
diff --git a/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductController.java b/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductController.java
index dbf187a..076721f 100644
--- a/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductController.java
+++ b/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductController.java
@@ -16,76 +16,53 @@
  */
 package org.apache.servicecomb.saga.demo.pack.inventory;
 
-import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
 
+@RequestMapping("/products")
 @Controller
-@RequestMapping("/product")
 public class ProductController {
-
-  private InventoryService inventoryService;
-
-  @PostMapping("/product")
-  public Response updateInventory(@RequestParam("productId") Long productId,
-      @RequestParam("requiredCount") Integer requiredCount) {
-    return new Response(0, "OK",
-        ImmutableList.of(inventoryService.reserve(productId, requiredCount)));
-  }
-
-  @ExceptionHandler
-  public Response exceptionHandler(Exception e) {
-    return new Response(-1, e.getMessage(),
-        ImmutableList.of(e.getStackTrace()));
-  }
-
   @Autowired
-  public void setInventoryService(
-      InventoryService inventoryService) {
-    this.inventoryService = inventoryService;
-  }
-}
-
-class Response {
-
-  private int code;
-
-  private String message;
-
-  private List<Object> objects;
-
-  Response(int code, String message, List<Object> objects) {
-    this.code = code;
-    this.message = message;
-    this.objects = objects;
-  }
-
-  public int getCode() {
-    return code;
-  }
-
-  public void setCode(int code) {
-    this.code = code;
-  }
+  private InventoryService inventoryService;
 
-  public String getMessage() {
-    return message;
+  private final AtomicInteger id = new AtomicInteger(0);
+
+  @PostMapping("/order/{userName}/{productName}/{amount}")
+  @ResponseBody
+  public ProductOrder updateInventory(@PathVariable String userName,
+      @PathVariable String productName, @PathVariable Integer amount) {
+    ProductOrder order = new ProductOrder();
+    order.setId(id.incrementAndGet());
+    order.setUserName(userName);
+    order.setProductName(productName);
+    order.setAmount(amount);
+    inventoryService.reserve(order);
+    return order;
   }
 
-  public void setMessage(String message) {
-    this.message = message;
+  @CrossOrigin
+  @GetMapping("/orders")
+  @ResponseBody
+  List<ProductOrder> getAll() {
+    return new ArrayList<>(inventoryService.getAllOrders());
   }
 
-  public List<Object> getObjects() {
-    return objects;
+  @DeleteMapping("/orders")
+  void clear() {
+    inventoryService.clearAllOrders();
+    id.set(0);
   }
 
-  public void setObjects(List<Object> objects) {
-    this.objects = objects;
-  }
 }
+
diff --git a/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductDao.java b/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductDao.java
index 42bcd4b..7a93664 100644
--- a/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductDao.java
+++ b/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductDao.java
@@ -16,8 +16,13 @@
  */
 package org.apache.servicecomb.saga.demo.pack.inventory;
 
+
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
 
 public interface ProductDao extends JpaRepository<Product, Long> {
 
+  @Query("select p from Product p where p.name =?1 ")
+  Product findProduceByName(String ProductName);
+
 }
diff --git a/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductOrder.java b/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductOrder.java
new file mode 100644
index 0000000..ebb074e
--- /dev/null
+++ b/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductOrder.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.servicecomb.saga.demo.pack.inventory;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+@JsonAutoDetect(fieldVisibility = Visibility.ANY)
+public class ProductOrder {
+  @JsonIgnore
+  private Integer id;
+  private String userName;
+  private String productName;
+  private Integer amount;
+  private boolean confirmed;
+  private boolean cancelled;
+
+
+  public String getUserName() {
+    return userName;
+  }
+
+  public void setUserName(String userName) {
+    this.userName = userName;
+  }
+
+  public String getProductName() {
+    return productName;
+  }
+
+  public void setProductName(String productName) {
+    this.productName = productName;
+  }
+
+  public Integer getAmount() {
+    return amount;
+  }
+
+  public void setAmount(Integer amount) {
+    this.amount = amount;
+  }
+
+  public boolean isConfirmed() {
+    return confirmed;
+  }
+
+  public void setConfirmed(boolean confirmed) {
+    this.confirmed = confirmed;
+  }
+
+  public boolean isCancelled() {
+    return cancelled;
+  }
+
+  public void setCancelled(boolean cancelled) {
+    this.cancelled = cancelled;
+  }
+
+  public Integer getId() {
+    return id;
+  }
+
+  public void setId(Integer id) {
+    this.id = id;
+  }
+}
diff --git a/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/TccInventoryApplication.java b/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/TccInventoryApplication.java
index 92c415c..878c156 100644
--- a/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/TccInventoryApplication.java
+++ b/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/TccInventoryApplication.java
@@ -22,8 +22,10 @@ import org.springframework.boot.CommandLineRunner;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Profile;
 
 @SpringBootApplication
+@Profile("prd")
 @EnableOmega
 public class TccInventoryApplication {
 
@@ -34,7 +36,10 @@ public class TccInventoryApplication {
   @Bean
   CommandLineRunner kickOff(ProductDao productDao) {
     return args -> {
-      productDao.save(new Product("Bottled water", 100));
+      // Set up the inventory
+      productDao.save(new Product("ProductA", 100));
+      productDao.save(new Product("ProductB", 10));
+      productDao.save(new Product("ProductC", 1));
     };
   }
 }
diff --git a/saga-demo/tcc-spring-demo/inventory/src/main/resources/application.yaml b/saga-demo/tcc-spring-demo/inventory/src/main/resources/application.yaml
index 98b79a6..fd05d0b 100644
--- a/saga-demo/tcc-spring-demo/inventory/src/main/resources/application.yaml
+++ b/saga-demo/tcc-spring-demo/inventory/src/main/resources/application.yaml
@@ -18,20 +18,15 @@ spring:
   application:
     name: inventory
   datasource:
-    url: jdbc:h2:mem:inventory-test
+    url: jdbc:h2:mem:inventory
   h2:
     console:
       enabled: true
   jpa:
-    show-sql: true
-    generate-ddl: true
-    hibernate:
-      ddl-auto: create-drop
-      naming:
-        physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
-        implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
+    properties:
+      eclipselink:
+        ddl-generation: none
+
 alpha:
   cluster:
-    address: ${alpha-server.servicecomb.io:localhost}:8080
-server:
-  port: 10020
\ No newline at end of file
+    address: alpha-server.servicecomb.io:8080
diff --git a/saga-demo/tcc-spring-demo/inventory/src/test/java/org/apache/servicecomb/saga/demo/pack/inventory/InventoryServiceTest.java b/saga-demo/tcc-spring-demo/inventory/src/test/java/org/apache/servicecomb/saga/demo/pack/inventory/InventoryServiceTest.java
new file mode 100644
index 0000000..0842f2c
--- /dev/null
+++ b/saga-demo/tcc-spring-demo/inventory/src/test/java/org/apache/servicecomb/saga/demo/pack/inventory/InventoryServiceTest.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.servicecomb.saga.demo.pack.inventory;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.fail;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+// Using test profile to avoid the Omega connection
+@ActiveProfiles("test")
+@SpringBootTest(classes = {TestApplication.class})
+public class InventoryServiceTest {
+  @Autowired
+  private InventoryService inventoryService;
+
+  @Autowired
+  private ProductDao productDao;
+
+  private ProductOrder order = new ProductOrder();
+
+  @Before
+  public void setup() {
+    productDao.save(new Product("ProductA", 100));
+    productDao.save(new Product("ProductB", 10));
+    productDao.save(new Product("ProductC", 1));
+
+  }
+
+  @After
+  public void cleanUp() {
+    productDao.deleteAll();
+    inventoryService.clearAllOrders();
+  }
+  
+  @Test
+  public void testInventoryServiceWithConfirmation() {
+    order.setId(1);
+    order.setUserName("user1");
+    order.setAmount(10);
+    order.setProductName("ProductA");
+    inventoryService.reserve(order);
+    assertThat(inventoryService.getInventory("ProductA"), is(90));
+    assertThat(inventoryService.getAllOrders().size(), is(1));
+
+    inventoryService.confirm(order);
+    assertThat(order.isCancelled(), is(false));
+    assertThat(order.isConfirmed(), is(true));
+    assertThat(inventoryService.getInventory("ProductA"), is(90));
+  }
+
+  @Test
+  public void getNoExitProductFromInventoryService() {
+    order.setId(1);
+    order.setAmount(10);
+    order.setProductName("Product");
+    try {
+      inventoryService.reserve(order);
+      fail("Expect exception here");
+    } catch (Exception ex) {
+      assertThat(ex.getMessage(), is("Product not exists at all!"));
+    }
+    assertThat(inventoryService.getAllOrders().size(), is(0));
+  }
+
+  @Test
+  public void getProductOutOfStockFromInventoryService() {
+    order.setAmount(10);
+    order.setProductName("ProductC");
+    try {
+      inventoryService.reserve(order);
+      fail("Expect exception here");
+    } catch (Exception ex) {
+      assertThat(ex.getMessage(), is("The Product is out of stock!"));
+    }
+    assertThat(inventoryService.getAllOrders().size(), is(0));
+  }
+
+  @Test
+  public void testInventoryServiceWithCancel() {order.setUserName("user1");
+    order.setId(1);
+    order.setAmount(10);
+    order.setProductName("ProductA");
+    inventoryService.reserve(order);
+    assertThat(inventoryService.getInventory("ProductA"), is(90));
+    assertThat(inventoryService.getAllOrders().size(), is(1));
+
+    inventoryService.cancel(order);
+    assertThat(order.isCancelled(), is(true));
+    assertThat(order.isConfirmed(), is(false));
+    assertThat(inventoryService.getAllOrders().size(), is(1));
+    assertThat(inventoryService.getInventory("ProductA"), is(100));
+  }
+
+}
diff --git a/saga-demo/tcc-spring-demo/inventory/src/test/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductControllerTest.java b/saga-demo/tcc-spring-demo/inventory/src/test/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductControllerTest.java
new file mode 100644
index 0000000..8dcc7dd
--- /dev/null
+++ b/saga-demo/tcc-spring-demo/inventory/src/test/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductControllerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.servicecomb.saga.demo.pack.inventory;
+
+import static java.util.Collections.singletonList;
+import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
+import static org.hamcrest.core.Is.is;
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+
+@RunWith(SpringRunner.class)
+@WebMvcTest(ProductController.class)
+public class ProductControllerTest {
+  private final ProductOrder someOrder = someOrder();
+
+  @Autowired
+  private MockMvc mockMvc;
+
+  @MockBean
+  private InventoryService inventoryService;
+
+  @Before
+  public void setUp() {
+    when(inventoryService.getAllOrders()).thenReturn(singletonList(someOrder));
+  }
+
+  @Test
+  public void retrievesOrdersFromRepo() throws Exception {
+    mockMvc.perform(get("/products/orders"))
+        .andExpect(status().isOk())
+        .andExpect(jsonPath("$", hasSize(1)))
+        .andExpect(jsonPath("$.[0].userName", is(someOrder.getUserName())))
+        .andExpect(jsonPath("$.[0].productName", is(someOrder.getProductName())))
+        .andExpect(jsonPath("$.[0].confirmed", is(someOrder.isConfirmed())))
+        .andExpect(jsonPath("$.[0].cancelled", is(someOrder.isCancelled())))
+        .andExpect(jsonPath("$.[0].amount", is(someOrder.getAmount())));
+  }
+
+  private ProductOrder someOrder() {
+    ProductOrder order = new ProductOrder();
+    order.setId(1);
+    order.setProductName("ProductName1");
+    order.setUserName("UserName");
+    order.setAmount(100);
+    order.setCancelled(false);
+    order.setConfirmed(true);
+    return order;
+  }
+
+}
diff --git a/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductDao.java b/saga-demo/tcc-spring-demo/inventory/src/test/java/org/apache/servicecomb/saga/demo/pack/inventory/TestApplication.java
similarity index 85%
copy from saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductDao.java
copy to saga-demo/tcc-spring-demo/inventory/src/test/java/org/apache/servicecomb/saga/demo/pack/inventory/TestApplication.java
index 42bcd4b..7a28136 100644
--- a/saga-demo/tcc-spring-demo/inventory/src/main/java/org/apache/servicecomb/saga/demo/pack/inventory/ProductDao.java
+++ b/saga-demo/tcc-spring-demo/inventory/src/test/java/org/apache/servicecomb/saga/demo/pack/inventory/TestApplication.java
@@ -14,10 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.servicecomb.saga.demo.pack.inventory;
 
-import org.springframework.data.jpa.repository.JpaRepository;
+package org.apache.servicecomb.saga.demo.pack.inventory;
 
-public interface ProductDao extends JpaRepository<Product, Long> {
+import org.springframework.boot.autoconfigure.SpringBootApplication;
 
+@SpringBootApplication
+public class TestApplication {
+  // Just for testing
 }
diff --git a/saga-demo/tcc-spring-demo/ordering/src/main/java/org/apache/servicecomb/saga/demo/pack/ordering/OrderService.java b/saga-demo/tcc-spring-demo/ordering/src/main/java/org/apache/servicecomb/saga/demo/pack/ordering/OrderService.java
new file mode 100644
index 0000000..99564aa
--- /dev/null
+++ b/saga-demo/tcc-spring-demo/ordering/src/main/java/org/apache/servicecomb/saga/demo/pack/ordering/OrderService.java
@@ -0,0 +1,4 @@
+package org.apache.servicecomb.saga.demo.pack.ordering;
+
+public class OrderService {
+}
diff --git a/saga-demo/tcc-spring-demo/payment/src/main/java/org/apache/servicecomb/saga/demo/pack/payment/Payment.java b/saga-demo/tcc-spring-demo/payment/src/main/java/org/apache/servicecomb/saga/demo/pack/payment/Payment.java
new file mode 100644
index 0000000..1349787
--- /dev/null
+++ b/saga-demo/tcc-spring-demo/payment/src/main/java/org/apache/servicecomb/saga/demo/pack/payment/Payment.java
@@ -0,0 +1,4 @@
+package org.apache.servicecomb.saga.demo.pack.payment;
+
+public class Payment {
+}