Challenge Overview

The supply server (ap-supply-server) is a pattern that was used early when we started building the new version of the topcoder microservice REST APIs.

The goal of the supply server was to centralize application initialization and configuration logic, as well as allow multiple microservices to be executed together in the same instance, which is handy for local development.

However, the supply server approach has several disadvantages mainly related with maintenance or multiple configuration files and complexity.

We want to remove the supply server from our microservices and use simple dropwizard instead. 

Concepts

Supply Library

The supply library is a shared library used by all of our microservices, among other things the supply library provides jdbc persistence and base classes that simplify the initialization and execution of dropwizard applications. The most relevant classes to use in the supply library are:

  • BaseApplication

    • This class is the superclass for all dropwizard application classes. It provides the entrypoint (main method) for the dropwizard application, and its subclasses are responsible for instantiating, initializing and registering all necessary resources for the microservice;

  • BaseAppConfiguration

    • This is the superclass for all dropwizard configuration classes. The configuration class is responsible for loading configs from the configuration yaml file and exposing it to the application. The subclasses of this class should have the same properties as the yaml file.

Runtime

Microservices are executed in AWS ElasticBeanstalk. They are built and deployed automatically using Jenkins jobs that poll github repositories.

Environments, configs, and build scripts

There are several build scripts and config files that are required to automatically build and deploy microservices. The supply server based approach requires a copy of some of these files for each of our environments, this is something that we want to change.

The run-config folder contains files for dev, qa, and prod environments (qa and prod folders are removed for security reason, but it is similar as dev folder). The main files in those folders are:

  • supply-server.yaml

    • Contains microservice specific configurations, such as datastore connection information, authentication domain, logging, etc.

    • For each environment there are different ip addresses, domain names, etc.

    • One of the problems that we have today is that the supply-server.yaml file contains some unused configuration because of the way other microservices use the same runtime when we run them together. We want the supply-server.yaml to be cleaned up and have only the configurations that a specific microservice requires.

  • sumo-logic

    • Sumo logic is used to centralize logs, and the config files defines the sumo logic behavior. Note, we want collectors with different names for each environment as: <microservice-name>-<environment>, e.g.: challenge-dev

  • NewRelic

    • New relic is used for monitoring, there are environment specific new relic configurations in the newrelic.yml file.

We want to consolidate these files, and have only one for all environments, in which all common configurations are stored. Environment specific configurations should be parameterized either with environment variables at runtime, or substitutions during build.

Also no private information should be stored in any of the configuration files, this includes database passwords, third party service keys like sumo logic and new relic, etc.

A file with environment specific private information to be used for substitution at build time can be introduced, and it will be stored in the jenkins server to be used by the build job.

Updating the build scripts is also in scope for this challenge, however, creating a jenkins job is not, it is enough if the build script can be executed from the command line given the necessary parameters, like the path to the file environment specific info.

Note that some environment specific and private information are injected to the supply-server.yml file using vm arguments e.g.:

java -Ddw.databases[0].password=123 … -jar service.jar server supply-server.yml

The above would set 123 to the password property below in supply-server.yml:

databases:

  - datasourceName: notificationsdb

    # the name of your JDBC driver

    driverClass: com.mysql.jdbc.Driver

    # the username

    user: notification

    # the password

    # NOTE: password is set as a java system property: i.e., -Ddw.database.password=<password>

    validationQuery: select 1

    # the JDBC URL

    url: jdbc:mysql://notification.ci8xwsszszsw.us-east-1.rds.amazonaws.com:3306/notification

Instructions

We have already removed the supply server from several of our microservices, and below are the steps to be performed:

  1. Obtain the supply library code, we’ll assume that it will be in the ap-supply-library folder;

  2. Obtain the code for the review microservice, we’ll assume that it will be in the ap-tagging-microservice folder;

  3. Build the supply library and install in the local cache, from the ap-supply-library folder do:

    1. mvn clean compile install

  4. In the ap-tagging-microservice folder, update the pom.xml file to make sure it uses the latest version of the supply library you have just built;

  5. In the microservice, create a microservice specific configuration class:

    1. It should be in the com.appirio.service.alerts package.

    2. It should be called AlertServiceConfiguration

    3. It should inherit from BaseAppConfiguration (part of the supply library)

    4. It should contain all the relevant properties from the supply-server.yml file to this specific microservice. Please analyse the code and delete any extraneous properties from the yml file and the configuration class.

  6. Create a microservice specific application class

    1. It should be in the com.appirio.service.alerts package

    2. It should be called AlertServiceApplication

    3. It should extend BaseApplication parameterized with the configuration class created in the previous step: e.g. : AlertServiceApplication extends  BaseApplication<AlertServiceConfiguration>

    4. The getName method should return the name of the microservice;

    5. The logServiceSpecificConfiguration should use the logger to output all configurations for the microservice that are NOT private information, i.e. don’t output tokens, keys, db passwords, etc. like

       /**
           * @see BaseApplication
           * @param config
           */
          @Override
          protected void logServiceSpecificConfigs(MemberServiceConfiguration config) {
              for(SupplyDatasourceFactory dbConfig : config.getDatabases()) {
                  logger.info("\tJDBI configuration ");
                  logger.info("\t\tDatabase config name : " + dbConfig.getDatasourceName());
                  logger.info("\t\tOLTP driver class : " + dbConfig.getDriverClass());
                  logger.info("\t\tOLTP connection URL : " + dbConfig.getUrl());
                  logger.info("\t\tOLTP Authentication user : " + dbConfig.getUser());
              }
       
              logger.info("\tElasticSearch configuration");
              logger.info("\t\tElasticSearch clustername : " + config.getEsConfiguration().getClusterName());
              logger.info("\t\tElasticsearch Host:Port : " + config.getEsConfiguration().getServers());
       
              logger.info("\r\n");
          }
    6. A main method should exist that simple invokes the run method, e.g.:
         public static void main(String[] args) throws Exception {
             new AlertServiceApplication().run(args);
         }

    7. The registerResources method should register all resources found in the supply-server.yml resources property. The supply server approach uses reflection to instantiate resources based on configuration, and we don’t want to use this approach anymore, we simple want the resources to be created and registered in a more traditional fashion, e.g.: 
         @Override
         protected void registerResources(ChallengeServiceConfiguration config, Environment env) throws Exception {
             // Register resources here
             env.jersey().register(new ChallengeFactory(config,
      env).getResourceInstance());
             env.jersey().register(new ChallengeResultsFactory(config, env).getResourceInstance());

    8. The prepare method should configure the databases: 
      configDatabases(config, config.getDatabases(), env);

  7. Add the execution plugin to the microservice pom.xml fixing the main class name:
    <build>
               <plugin>
                   <groupId>org.apache.maven.plugins</groupId>
                   <artifactId>maven-shade-plugin</artifactId>
                   <version>2.3</version>
                   <configuration>
                       <createDependencyReducedPom>true</createDependencyReducedPom>
                       <filters>
                           <filter>
                               <artifact>*:*</artifact>
                               <excludes>
                                   <exclude>META-INF/*.SF</exclude>
                                   <exclude>META-INF/*.DSA</exclude>
                                   <exclude>META-INF/*.RSA</exclude>
                               </excludes>
                           </filter>
                       </filters>
                   </configuration>
                   <executions>
                       <execution>
                           <phase>package</phase>
                           <goals>
                               <goal>shade</goal>
                           </goals>
                           <configuration>
                               <transformers>
                                   <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
                                   <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
    <mainClass>com.appirio.service.challenge.ChallengeServiceApplication</mainClass>
                                   </transformer>
                               </transformers>
                           </configuration>
                       </execution>
                   </executions>
               </plugin>
           </plugins>

  8. Add mysql dependency and both springsource repositories to the microservice pom.xml:
        <dependencies>        

    <dependency>

                            <groupId>mysql</groupId>

                            <artifactId>mysql-connector-java</artifactId>

                            <version>5.1.34</version>

                    </dependency>


            
        <repositories>
           <repository>
               <id>com.springsource.repository.bundles.release</id>
               <name>SpringSource Enterprise Bundle Repository - SpringSource Bundle
    Releases</name>
               <url>http://repository.springsource.com/maven/bundles/release</url>
           </repository>
           <repository>
               <id>com.springsource.repository.bundles.external</id>
               <name>SpringSource Enterprise Bundle Repository - External Bundle
    Releases</name>
               <url>http://repository.springsource.com/maven/bundles/external</url>
           </repository>
        ...

  9. Copy the supply-server.yml file from any of the environment in run-config, to the src/main/resources folder and rename it to <microservice>.yml, e.g. challenge-service.yml. and clean up the properties.

  10. Create a service/src/main/resources/new-relic-url-patterns file, it should contain a list of all endpoints exposed by this microservice. Refer to the resources to determine the urls, or you can just run the microservice, dropwizard will output all url when it is loading up. Example url patterns file:
    GET members\/.*\/challenges\/
    GET members\/.*\/srms\/
    GET members\/.*\/mms\/
    GET members\/.*\/
    PUT members\/.*\/
    GET challengeResults\/
    GET challenges\/test\/
    GET challenges\/
    GET marathonMatches\/
    GET marathonMatchResults\/
    GET srms\/
    GET srmResults\/

  11. Move service/run-config/dev/build to root directory

  12. Remove the service/run-config/run-config folder, as it is redundent.

  13. Code coverage must not decrease;

  14. All classes, methods and fields must have descriptive javadocs. Any method with more than 10 lines must have inline comments.

  15. add a README.md file decribing the configurations and how to run the microservice, including the verification steps. 

Verification

  1. We’ll use docker to run mysql in your box, so if you don’t have docker follow the steps at https://docs.docker.com/engine/installation/

  2. Build the microservice. From the microservice folder do:
    mvn clean compile package

  3. Configure the microservice to connect to mysql

  4. Run the microservice. From the microservice folder: 
    java <jvm params> -jar target/<microservice>.jar server src/main/resources/<microservice>.yml
    Note: <jvm params> should include any environment specific parameters that are not hardcoded in the yml: -Ddw.<property-name>=<value>
     

If the service starts successfully, you should see log messages similar to the ones below, displaying a list of registered endpoints specific to your service. If the service starts cleanly and you see the entries for all resources of the microservice, the service has been migrated correctly. Here is the example logs generated when the member microservice starts:

   INFO  [2016-04-08 03:57:51,305] io.dropwizard.jersey.DropwizardResourceConfig: The following paths were found for the configured resources:

   GET     /v3/members/{handle}/financial (com.appirio.service.member.resources.MemberFinancialResource)

   GET     /v3/members/{handle} (com.appirio.service.member.resources.MemberProfileResource)

   POST    /v3/members/{handle}/photoUploadUrl (com.appirio.service.member.resources.MemberProfileResource)

   PUT     /v3/members/{handle} (com.appirio.service.member.resources.MemberProfileResource)

   PUT     /v3/members/{handle}/photo (com.appirio.service.member.resources.MemberProfileResource)

   GET     /v3/members/{handle}/stats (com.appirio.service.member.resources.MemberStatsResource)

   GET     /v3/members/{handle}/stats/history (com.appirio.service.member.resources.MemberHistoryStatsResource)

   GET     /v3/members/stats/distribution (com.appirio.service.member.resources.MemberDistributionStatsResource)

   GET     /v3/members/{handle}/skills (com.appirio.service.member.resources.MemberSkillsResource)

   PATCH   /v3/members/{handle}/skills (com.appirio.service.member.resources.MemberSkillsResource)

   GET     /v3/members/{handle}/externalAccounts (com.appirio.service.member.resources.MemberExternalAccountsResource)

   DELETE  /v3/members/{handle}/externalLinks/{key} (com.appirio.service.member.resources.MemberExternalLinksResource)

   GET     /v3/members/{handle}/externalLinks (com.appirio.service.member.resources.MemberExternalLinksResource)

   POST    /v3/members/{handle}/externalLinks (com.appirio.service.member.resources.MemberExternalLinksResource)

   INFO  [2016-04-08 03:57:51,537] org.eclipse.jetty.server.handler.ContextHandler: Started i.d.j.MutableServletContextHandler@12fccff0{/,null,AVAILABLE}

   INFO  [2016-04-08 03:57:51,541] io.dropwizard.setup.AdminEnvironment: tasks =

   POST    /tasks/gc (io.dropwizard.servlets.tasks.GarbageCollectionTask)

   INFO  [2016-04-08 03:57:51,547] org.eclipse.jetty.server.handler.ContextHandler: Started i.d.j.MutableServletContextHandler@7babed9e{/,null,AVAILABLE}

   INFO  [2016-04-08 03:57:51,551] org.eclipse.jetty.server.ServerConnector: Started application@31e739bf{HTTP/1.1}{0.0.0.0:8080}

   INFO  [2016-04-08 03:57:51,552] org.eclipse.jetty.server.ServerConnector: Started admin@29079032{HTTP/1.1}{0.0.0.0:8081}



Final Submission Guidelines

  • Added/Updated files
  • A changed file describing the added, updated and deleted files and directories.
  • added README.md file decribing the configurations and how to run the microservice, including the verification steps. No additional Deployment Guide document needed.

ELIGIBLE EVENTS:

2016 TopCoder(R) Open

REVIEW STYLE:

Final Review:

Community Review Board

Approval:

User Sign-Off

SHARE:

ID: 30053747