All Products
Search
Document Center

Develop applications

Last Updated: Feb 26, 2019

Service registration and discovery

The following describes how to use Pandora Boot to develop applications and implement service registration and discovery.

Download the demo source code sc-hsf-provider and sc-hsf-consumer.

Create a service provider

  1. Create a Maven project named sc-hsf-provider.

  2. Introduce the necessary dependencies to the pom.xml file:

    1. <parent>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-parent</artifactId>
    4. <version>1.5.8.RELEASE</version>
    5. <relativePath/>
    6. </parent>
    7. <dependencies>
    8. <dependency>
    9. <groupId>org.springframework.cloud</groupId>
    10. <artifactId>spring-cloud-starter-hsf</artifactId>
    11. <version>1.3</version>
    12. </dependency>
    13. <dependency>
    14. <groupId>org.springframework.cloud</groupId>
    15. <artifactId>spring-cloud-starter-pandora</artifactId>
    16. <version>1.3</version>
    17. </dependency>
    18. <dependency>
    19. <groupId>org.springframework.boot</groupId>
    20. <artifactId>spring-boot-starter-web</artifactId>
    21. </dependency>
    22. </dependencies>
    23. <dependencyManagement>
    24. <dependencies>
    25. <dependency>
    26. <groupId>org.springframework.cloud</groupId>
    27. <artifactId>spring-cloud-dependencies</artifactId>
    28. <version>Dalston.SR4</version>
    29. <type>pom</type>
    30. <scope>import</scope>
    31. </dependency>
    32. </dependencies>
    33. </dependencyManagement>

    Although the HSF service framework is independent of the web environment, web-related features are required when EDAS is used to manage the lifecycle of applications. Therefore, you must add a dependency for spring-boot-starter-web.

    If you do not want to configure the parent of the project as spring-boot-starter-parent, you can add dependencyManagement and set scope=import as follows to manage dependency versions.

    1. <dependencyManagement>
    2. <dependencies>
    3. <dependency>
    4. <groupId>org.springframework.boot</groupId>
    5. <artifactId>spring-boot-dependencies</artifactId>
    6. <version>1.5.8.RELEASE</version>
    7. <type>pom</type>
    8. <scope>import</scope>
    9. </dependency>
    10. </dependencies>
    11. </dependencyManagement>
  3. Define a service API, and create an API class of com.aliware.edas.EchoService.

    1. public interface EchoService {
    2. String echo(String string);
    3. }

    The HSF service framework enables service communication based on APIs. When an API is defined, producers use this API to implement and release specific services, and consumers also use this API to subscribe to and consume services.

    The API com.aliware.edas.EchoService provides an echo method, which also means that the service com.aliware.edas.EchoService provides an echo method.

  4. Add the implementation class EchoServiceImpl of the service provider, and publish the service using annotations.

    1. @HSFProvider(serviceInterface = EchoService.class, serviceVersion = "1.0.0")
    2. public class EchoServiceImpl implements EchoService {
    3. @Override
    4. public String echo(String string) {
    5. return string;
    6. }
    7. }

    In addition to the API name serviceInterface, HSF also requires the serviceVersion (service version) to uniquely identify a service. In this case, the serviceVersion attribute in the HSFProvider annotation is set to “1.0.0”.Then, the service to be published can be identified by the serviceInterface com.aliware.edas.EchoService and serviceVersion 1.0.0 combination.

    The configuration in the HSFProvider annotation has the highest priority. If it is not configured in the HSFProvider annotation, the global configuration of these properties is checked in the file resources/application.properties when the service is published.If neither is configured, the default values in the HSFProvider annotation are used.

  5. Configure the application name and the listener port number in the application.properties file in resources.

    1. spring.application.name=hsf-provider
    2. server.port=18081
    3. spring.hsf.version=1.0.0
    4. spring.hsf.timeout=3000

    Best practices: We recommend that you configure both the service version and service timeout in the application.properties file.

  6. Add main function entrance for starting the service.

    1. @SpringBootApplication
    2. public class HSFProviderApplication {
    3. public static void main(String[] args) {
    4. // Start Pandora Boot to load the Pandora container
    5. PandoraBootstrap.run(args);
    6. SpringApplication.run(ServerApplication.class, args);
    7. // This indicates that the service has been started, and a thread waiting time is set.This prevents the container from exiting due to users who exit after running the service code.
    8. PandoraBootstrap.markStartupAndWait();
    9. }
    10. }

Create a service consumer

In this example, we create a service consumer that calls the service provider using the API provided by HSFProvider.

  1. Create a Maven project named sc-hsf-consumer.

  2. Introduce the necessary dependencies to the pom.xml file:

    The Maven dependencies for HSFConsumer and HSFProvider are exactly the same.

    1. <parent>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-parent</artifactId>
    4. <version>1.5.8.RELEASE</version>
    5. <relativePath/>
    6. </parent>
    7. <dependencies>
    8. <dependency>
    9. <groupId>org.springframework.cloud</groupId>
    10. <artifactId>spring-cloud-starter-hsf</artifactId>
    11. <version>1.3</version>
    12. </dependency>
    13. <dependency>
    14. <groupId>org.springframework.cloud</groupId>
    15. <artifactId>spring-cloud-starter-pandora</artifactId>
    16. <version>1.3</version>
    17. </dependency>
    18. <dependency>
    19. <groupId>org.springframework.boot</groupId>
    20. <artifactId>spring-boot-starter-web</artifactId>
    21. </dependency>
    22. </dependencies>
    23. <dependencyManagement>
    24. <dependencies>
    25. <dependency>
    26. <groupId>org.springframework.cloud</groupId>
    27. <artifactId>spring-cloud-dependencies</artifactId>
    28. <version>Dalston.SR4</version>
    29. <type>pom</type>
    30. <scope>import</scope>
    31. </dependency>
    32. </dependencies>
    33. </dependencyManagement>
  3. Copy the service API (including the package name) com.aliware.edas.EchoService published by the service provider to a local machine.

    1. public interface EchoService {
    2. String echo(String string);
    3. }
  4. Use an annotation, to inject the service consumer instance into the Context of Spring.

    1. @Configuration
    2. public class HsfConfig {
    3. @HSFConsumer(clientTimeout = 3000, serviceVersion = "1.0.0")
    4. private EchoService echoService;
    5. }

    Best practices: Configure @HSFConsumer once in the Config class, and inject and use it in multiple places through @Autowired.Usually, an HSF consumer is used in multiple places, but you do not have to mark each place where it is used with @HSFConsumer.You can write a unified Config class and inject it directly wherever it is needed by using @Autowired.

  5. To facilitate the test, an HTTP API of /hsf-echo/* is exposed through the SimpleController. Calls to the HSF service provider are internally implemented in the API /hsf-echo/*.

    1. @RestController
    2. public class SimpleController {
    3. @Autowired
    4. private EchoService echoService;
    5. @RequestMapping(value = "/hsf-echo/{str}", method = RequestMethod.GET)
    6. public String echo(@PathVariable String str) {
    7. return echoService.echo(str);
    8. }
    9. }
  6. Configure the application name and the listener port in the application.properties file in resources.

    1. spring.application.name=hsf-consumer
    2. server.port=18082
    3. spring.hsf.version=1.0.0
    4. spring.hsf.timeout=1000

    Best practices: We recommend that you configure both the service version and service timeout in the application.properties file.

  7. Add main function entrance for starting the service.

    1. @SpringBootApplication
    2. public class HSFConsumerApplication {
    3. public static void main(String[] args) {
    4. PandoraBootstrap.run(args);
    5. SpringApplication.run(HSFConsumerApplication.class, args);
    6. PandoraBootstrap.markStartupAndWait();
    7. }
    8. }

Local development and debugging

Start the lightweight configuration center

The lightweight configuration center, which includes a lightweight version of the EDAS service registration and discovery server, must be started for local development and debugging. For more information, see the lightweight configuration center.

Start the application

The application can be locally started in two ways.

  • Start in IDE

    Configure the startup parameter -Djmenv.tbsite.net={$IP} in VM options, and start the application directly by using the main method.Here, {$IP} indicates the address of the computer on which the lightweight configuration center is started.For example, if the center is started on the current computer, $IP is 127.0.0.1.

    Rather than configuring JVM parameters, you can also directly modify the hosts file to bind jmenv.tbsite.net to the IP address of the instance on which the lightweight configuration center is started.For more information, see the lightweight configuration center.

  • Start with FatJar

    1. Add the FatJar packaging plugin.

      To package the pandora-boot project into FatJar with Maven, add the following plugin in the pom.xml file.

      To prevent conflicts with other packaging plugins, do not add any other FatJar plugins to the build plugin.

      1. <build>
      2. <plugin>
      3. <groupId>com.taobao.pandora</groupId>
      4. <artifactId>pandora-boot-maven-plugin</artifactId>
      5. <version>2.1.9.1</version>
      6. <executions>
      7. <execution>
      8. <phase>package</phase>
      9. <goals>
      10. <goal>repackage</goal>
      11. </goals>
      12. </execution>
      13. </executions>
      14. </plugin>
      15. </build>
    2. After adding the plugins, run the Maven command mvn clean package in the home directory of the project to create a package. The created FatJar file is in the target directory.

    3. Run the Java command to start the application.

      1. java -Djmenv.tbsite.net=127.0.0.1 -Dpandora.location=/Users/{$username}/.m2/repository/com/taobao/pandora/taobao-hsf.sar/dev-SNAPSHOT/taobao-hsf.sar-dev-SNAPSHOT.jar -jar sc-hsf-provider-0.0.1-SNAPSHOT.jar

      Note: The path specified by -Dpandora.location must be a full path followed by sc-hsf-provider-0.0.1-SNAPSHOT.jar.

Demonstration

Enable the service and check whether it can be called.

 Basic HSF demonstration

Asynchronous calls

HSF enables two types of asynchronous calling, Future and Callback.

  1. To demonstrate asynchronous calls, we have published a new service: com.aliware.edas.async.AsyncEchoService.

    1. public interface AsyncEchoService {
    2. String future(String string);
    3. String callback(String string);
    4. }
  2. The service provider implements AsyncEchoService and uses annotations to publish it.

    1. @HSFProvider(serviceInterface = AsyncEchoService.class, serviceVersion = "1.0.0")
    2. public class AsyncEchoServiceImpl implements AsyncEchoService {
    3. @Override
    4. public String future(String string) {
    5. return string;
    6. }
    7. @Override
    8. public String callback(String string) {
    9. return string;
    10. }
    11. }

    Likewise, the subsequent configuration steps and application startup processes are the same.

    Note: The logic of asynchronous calls is modified on the consumer rather than the server.

Future

  1. To enable Future-type asynchronous calls for the consumer end, use annotations to inject instances of the service consumer into Context of Spring, and configure the method name of asynchronous calls in futureMethods properties of @HSFConsumer annotations.

    This Future method of com.aliware.edas.async.AsyncEchoService is marked as Future-type asynchronous calls.

    1. @Configuration
    2. public class HsfConfig {
    3. @HSFConsumer(serviceVersion = "1.0.0", futureMethods = "future")
    4. private AsyncEchoService asyncEchoService;
    5. }
  2. After the method is marked as Future-type asynchronous calls, the actual return value of the method during synchronous execution is null, and the call result must be obtained through HSFResponseFuture.

    TestAsyncController is used for demonstration. The sample code is as follows:

    1. @RestController
    2. public class TestAsyncController {
    3. @Autowired
    4. private AsyncEchoService asyncEchoService;
    5. @RequestMapping(value = "/hsf-future/{str}", method = RequestMethod.GET)
    6. public String testFuture(@PathVariable String str) {
    7. String str1 = asyncEchoService.future(str);
    8. String str2;
    9. try {
    10. HSFFuture hsfFuture = HSFResponseFuture.getFuture();
    11. str2 = (String) hsfFuture.getResponse(3000);
    12. } catch (Throwable t) {
    13. t.printStackTrace();
    14. str2 = "future-exception";
    15. }
    16. return str1 + " " + str2;
    17. }
    18. }

    Call /hsf-future/123; the str1 value is null, and str2 value is 123, which is the actual return value.

     Single /hsf-future/123 call result

  3. If a series of operation return values are needed for service processing, refer to the following call method.

    1. @RequestMapping(value = "/hsf-future-list/{str}", method = RequestMethod.GET)
    2. public String testFutureList(@PathVariable String str) {
    3. try {
    4. int num = Integer.parseInt(str);
    5. List<String> params = new ArrayList<String>();
    6. for (int i = 1; i <= num; i++) {
    7. params.add(i + "");
    8. }
    9. List<HSFFuture> hsfFutures = new ArrayList<HSFFuture>();
    10. for (String param : params) {
    11. asyncEchoService.future(param);
    12. hsfFutures.add(HSFResponseFuture.getFuture());
    13. }
    14. ArrayList<String> results = new ArrayList<String>();
    15. for (HSFFuture hsfFuture : hsfFutures) {
    16. results.add((String) hsfFuture.getResponse(3000));
    17. }
    18. return Arrays.toString(results.toArray());
    19. } catch (Throwable t) {
    20. return "exception";
    21. }
    22. }

     Multiple /hsf-future/123 call results

Callback

  1. To enable Callback-type asynchronous calls for the consumer end, create a class to implement the HSFResponseCallback API and use @Async annotations for configuration.

    1. @AsyncOn(interfaceName = AsyncEchoService.class,methodName = "callback")
    2. public class AsyncEchoResponseListener implements HSFResponseCallback{
    3. @Override
    4. public void onAppException(Throwable t) {
    5. t.printStackTrace();
    6. }
    7. @Override
    8. public void onAppResponse(Object appResponse) {
    9. System.out.println(appResponse);
    10. }
    11. @Override
    12. public void onHSFException(HSFException hsfEx) {
    13. hsfEx.printStackTrace();
    14. }
    15. }

    AsyncEchoResponseListener implements the HSFResponseCallback API, and sets the interfaceName to AsyncEchoService.class and the methodName to callback in the @Async annotation.

    The Callback method of com.aliware.edas.async.AsyncEchoService is marked as Callback-type asynchronous calls.

  2. Similarly, TestAsyncController is used for demonstration. The sample code is as follows:

    1. @RequestMapping(value = "/hsf-callback/{str}", method = RequestMethod.GET)
    2. public String testCallback(@PathVariable String str) {
    3. String timestamp = System.currentTimeMillis() + "";
    4. String str1 = asyncEchoService.callback(str);
    5. return str1 + " " + timestamp;
    6. }

    After the feature is called, the following result is returned:

     Multiple /hsf-callback/123 call results

    After the consumer end configures the callback method to Callback-type asynchronous calling, the synchronous return value is actually null.

    After the result is returned, HSF calls the method in AsyncEchoResponseListener, and the actual return value of calling can be obtained using the onAppResponse method.

  3. Use CallbackInvocationContext to transmit the contextual information of the calling to Callback.

    The sample code for calling is as follows:

    1. CallbackInvocationContext.setContext(timestamp);
    2. String str1 = asyncEchoService.callback(str);
    3. CallbackInvocationContext.setContext(null);

    The sample code of AsyncEchoResponseListener is as follows:

    1. @Override
    2. public void onAppResponse(Object appResponse) {
    3. Object timestamp = CallbackInvocationContext.getContext();
    4. System.out.println(timestamp + " " +appResponse);
    5. }

    The output on the console is 1513068791916 123, which means that the onAppResponse method of AsyncEchoResponseListener has used the CallbackInvocationContext to receive the timestamp transferred before the call.

    hsf callback console

Perform the unit test

The implementation of spring-cloud-starter-hsf depends on Pandora Boot, and unit testing of Pandora Boot is enabled through PandoraBootRunner and seamlessly integrated with SpringJUnit4ClassRunner.

The procedure of unit testing in service providers is demonstrated as follows for your reference.

  1. Add dependency for spring-boot-starter-test in Maven.

    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-test</artifactId>
    4. </dependency>
  2. Develop the test code.

    1. @RunWith(PandoraBootRunner.class)
    2. @DelegateTo(SpringJUnit4ClassRunner.class)
    3. // Add the test class. In this case, both the startup class of Spring Boot and this test class are required.
    4. @SpringBootTest(classes = {HSFProviderApplication.class, EchoServiceTest.class })
    5. @Component
    6. public class EchoServiceTest {
    7. /**
    8. * If you are using @HSFConsumer, you must add this class to the @SpringBootTest classes and use it to inject objects to prevent abnormal class conversion during generalization.
    9. */
    10. @HSFConsumer(generic = true)
    11. EchoService echoService;
    12. //Common calls
    13. @Test
    14. public void testInvoke() {
    15. TestCase.assertEquals("hello world", echoService.echo("hello world"));
    16. }
    17. //Generic calls
    18. @Test
    19. public void testGenericInvoke() {
    20. GenericService service = (GenericService) echoService;
    21. Object result = service.$invoke("echo", new String[] {"java.lang.String"}, new Object[] {"hello world"});
    22. TestCase.assertEquals("hello world", result);
    23. }
    24. //Return the value Mock
    25. @Test
    26. public void testMock() {
    27. EchoService mock = Mockito.mock(EchoService.class, AdditionalAnswers.delegatesTo(echoService));
    28. Mockito.when(mock.echo("")).thenReturn("beta");
    29. TestCase.assertEquals("beta", mock.echo(""));
    30. }
    31. }