Vision Statement

Create a very lightweight email micro-service that will be self-contained, easy to deploy and integrate with. Micro-service could not use any out of the box email servers or email as a service solutions.

Use Case

Suppose, you have an application that needs to send user emails to update a forgotten password or to invite new users to the system. The email capability of your application should be designed as a shared component that can be later extended to send text messages. Email templates should be consistent across different applications and maintained in one place. Email server configuration can be complex, so it needs to be isolated and easily reproducible.

Design Considerations

Spring boot or no spring boot, that is the question. For the itzap-message micro-service, I choose Jersey with hk2 as dependency injection framework. It turns out, that it is quite easy to build executable Jar micro-service with embedded Tomcat without spring/spring-boot. As a result, executable Jar is a lot smaller, faster to load, and deploy. Another component of the system is an email server. To solve email server configuration problems I looked for a Docker container that provides an email server. The Docker container that I found was boky/postfix . Now, to build the application, I can use docker-compose to put everything together.

Implementation Details

Jersey, Tomcat, and HK2

To start building micro-service using Jersey, Tomcat, and HK2 drop theses dependencies in your pom.xml file.

<dependency>
  <groupId>org.apache.tomcat.embed</groupId>
  <artifactId>tomcat-embed-core</artifactId>
  <version>${tomcat.version}</version>
</dependency>

<dependency>
  <groupId>org.apache.tomcat.embed</groupId>
  <artifactId>tomcat-embed-jasper</artifactId>
  <version>${tomcat.version}</version>
</dependency>
<dependency>
  <groupId>org.apache.tomcat</groupId>
  <artifactId>tomcat-jasper</artifactId>
  <version>${tomcat.version}</version>
</dependency>
<dependency>
  <groupId>org.apache.tomcat</groupId>
  <artifactId>tomcat-jasper-el</artifactId>
  <version>${tomcat.version}</version>
</dependency>

<dependency>
  <groupId>org.glassfish.jersey.media</groupId>
  <artifactId>jersey-media-json-jackson</artifactId>
  <version>${jersey2.version}</version>
</dependency>

<dependency>
  <groupId>org.glassfish.jersey.containers</groupId>
  <artifactId>jersey-container-servlet</artifactId>
  <version>${jersey2.version}</version>
</dependency>

<dependency>
  <groupId>org.glassfish.jersey.inject</groupId>
  <artifactId>jersey-hk2</artifactId>
  <version>${jersey2.version}</version>
</dependency>

<dependency>
  <groupId>org.glassfish.jersey.bundles</groupId>
  <artifactId>jaxrs-ri</artifactId>
  <version>${jersey2.version}</version>
</dependency>

<dependency>
  <groupId>org.glassfish.hk2</groupId>
  <artifactId>hk2-extras</artifactId>
  <version>${hk2.version}</version>
</dependency>

Application Class

The main application class looks like this:

public class EmailApplication
{
  private static final Logger LOGGER = LoggerFactory.getLogger(EmailApplication.class);

  public static void main(String[] args) {
    try {
      start();
    } catch (Exception e) {
      LOGGER.error("ITZap Email application failed to run", e);
    }
  }

  private static void start() throws Exception {

    String contextPath = "";
    String appBase = ".";
    String port = System.getenv("PORT");
    if (port == null || port.isEmpty()) {
      port = "8025";
    }

    Tomcat tomcat = new Tomcat();
    tomcat.setPort(Integer.parseInt(port));
    tomcat.getHost().setAppBase(appBase);

    Context context = tomcat.addWebapp(contextPath, appBase);

    Tomcat.addServlet(context, "jersey-container-servlet",
                      new ServletContainer(resourceConfig()));
    context.addServletMappingDecoded(UDecoder.URLDecode("/v1/itzap/message/*", Charset.defaultCharset()),
                                     "jersey-container-servlet");

    // initialize Tomcat. (Since version 9.xx need to add tomcat.getConnector() here)
    tomcat.getConnector();
    tomcat.start();
    tomcat.getServer().await();
  }

  private static ResourceConfig resourceConfig() {
    return new JerseyConfig();
  }
}

HK2 Dependency Injection

I drop all factories as static nested classes in one Factories class for convenience.

public final class Factories {
  private static final Config CONFIG = ConfigFactory.load();

  private Factories() {}

  public static class MailServiceFactory extends AbstractFactory<EMailService> {
    public MailServiceFactory() {
    }

    @Override
    public EMailService provide() {
      return new EMailService(CONFIG);
    }
  }
}

Put it all together.

public class JerseyConfig extends ResourceConfig {
  JerseyConfig() {
    register(JacksonFeature.class);

    // register endpoints
    packages("com.itzap.message.rest");

    register(new ContainerLifecycleListener()
             {
               public void onStartup(Container container)
               {
                 // access the ServiceLocator here
                 // serviceLocator = container.getApplicationHandler().
                 ServiceLocator serviceLocator = ((ImmediateHk2InjectionManager)container
                                                  .getApplicationHandler().getInjectionManager()).getServiceLocator();
                 enableTopicDistribution(serviceLocator);
                 // ... do what you need with ServiceLocator ...
               }

               public void onReload(Container container) {/*...*/}
               public void onShutdown(Container container) {/*...*/}
             });

    register(new AbstractBinder() {
      @Override
      protected void configure() {
        // hk2 bindings
        bindFactory(Factories.MailServiceFactory.class)
          .to(EMailService.class)
          .in(Singleton.class);
      }
    });
  }
}

More Jersey

One of the features of Jersey that I like is @Provider mechanism. Here is an example of how to centralize error handling in the Jersey application.

@Provider
public class IZapExceptionMapper implements ExceptionMapper<IZapException> {
  @Override
  public Response toResponse(IZapException e) {
    ErrorResponse response = MapperUtils.toReturn(e);

    return Response.status(response.getStatus())
      .entity(response)
      .type(MediaType.APPLICATION_JSON_TYPE)
      .build();
  }
}

Not shown here, but Jersey can actually inject beans into @Provider classes.

Other

I think one of the undervalued configuration/properties management libraries is com.typesafe I like this library for the ease of use, handling property hierarchies, flexibility, and out of the box property loading convention.

<dependency>
  <groupId>com.typesafe</groupId>
  <artifactId>config</artifactId>
</dependency>
private static final Config CONFIG = ConfigFactory.load();
// do some stuff
String emailFrom = CONFIG.getString("email.from");
  

Build

Some manual work needs to be done to package an application into a single executable Jar. Take a look at the following maven plugin:

<plugin>
  <artifactId>maven-assembly-plugin</artifactId>
  <version>3.0.0</version>
  <executions>
    <execution>
      <id>make-assembly</id> <!-- this is used for inheritance merges -->
      <phase>package</phase> <!-- bind to the packaging phase -->
      <goals>
        <goal>single</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <archive>
      <manifest>
        <mainClass>com.itzap.message.app.EmailApplication</mainClass>
      </manifest>
    </archive>
    <descriptors>
      <descriptor>src/main/resources/assembly/itzap-message.xml</descriptor>
    </descriptors>
    <appendAssemblyId>false</appendAssemblyId>
  </configuration>
</plugin>

This plugin is using itzap-message.xml descriptor file

<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
    <id>itzap-message</id>
    <formats>
        <format>jar</format>
    </formats>

    <includeBaseDirectory>false</includeBaseDirectory>
    <fileSets>
        <fileSet>
            <directory>src</directory>
            <excludes>
                <exclude>**</exclude>
            </excludes>
        </fileSet>
    </fileSets>

    <dependencySets>
        <dependencySet>
            <unpack>true</unpack>
            <unpackOptions>
                <excludes>
                    <exclude>*.log</exclude>
                    <exclude>assembly/**</exclude>
                    <exclude>databases/**</exclude>
                </excludes>
            </unpackOptions>
        </dependencySet>
    </dependencySets>
</assembly>

I usually, unpack all dependencies and merge them into a final jar. There are other strategies for building executable Jar that can be configured in the descriptor file.

Deployment

I already mentioned adocker-compose as a mechanism for running itzap-message micro-service. Here is an example of the docker-compose.yml

version: '3.2'

services:

  mail:
    image: boky/postfix
    environment:
      ALLOWED_SENDER_DOMAINS: ${ALLOWED_DOMAINS}

    ports:
      - 1587:587

  message:
    # Builds message service
    build: "."
    image: message-service:latest
    environment:

      MAIL_HOST: mail
      MAIL_FROM: ${MAIL_FROM}
    privileged: true
    ports:
      - 8025:8025
    links:
      - mail

    depends_on:
      - mail

Here, you can see two containers mail and message. Mail container comes straight from the Docker Hub boky/postfix while message is a simple java contaner defined in the following Dockerfile

# Alpine Linux with OpenJDK JRE
FROM openjdk:8-jre-alpine
COPY message-impl/target/message-impl-0.0.1-SNAPSHOT.jar /opt/lib/
ENTRYPOINT ["/usr/bin/java"]
CMD ["-jar", "/opt/lib/message-impl-0.0.1-SNAPSHOT.jar"]
EXPOSE 8025

Notes

itzap-message micro-service application contains two modules: message-api and message-impl. Assuming application consuming itzap-message micro-service is a Java application, having message-api jar streamlines the development of the Java client. API library provides common POJOs and a service interface that can be implemented on the client-side.

Readme

itzap-message

itzap-message micro-service project designed to send email messages. Visit my ITZap blog to read more about this project.

Usage

itzap-message desinged to run in a Docker container along with postfix that is also running in a Docker. Using docker-compose itzap-message can be deployed anyware and ready to send emails.

How To Build

  • Clone the following projects:
    • git clone git@github.com:avinokurov/itzap-parent.git
    • git clone git@github.com:avinokurov/itzap-common.git
    • git clone git@github.com:avinokurov/itzap-rxjava.git
    • git clone git@github.com:avinokurov/itzap-message.git
  • Build all projects
    • cd itzap-parent && mvn clean install
    • cd ../itzap-common && mvn clean install
    • cd ../itzap-rxjava && mvn clean install
    • cd ../itzap-message && mvn clean install
  • Running
    • Before running set environment variables export MAIL_FROM = mailer@test.com to whater email address will be used to send messages from and export ALLOWED_DOMAINS=test.com domain used to send emails.
    • To run the itzap-message micro-service docker-compose up
  • Testing
    • Once both Docker containers are running open Postman and call micro-service API to send an email POST http://localhost:8025/v1/itzap/message/email Body
     	{
     		"messageId": "new-user",
     		"subject": "test",
     		"addresses": ["avinokurov@itzap.com"],
     		"transport": "email"
     	}

Code

Command Pattern

Command pattern can be extended using RxJava to create a robust application code. This idea is an extension of the NetflixObservableCommand.

Vision Statement

Create a lightweight extensible library that implements a command pattern with RxJava extension. This library should provide an easy way to create commands and turning them into Reactive Observables. Commands will wrap exception handling and provide execution time metric for tracability.

Use Case

In Service-Oriented Programming, it is a common need to combine results of one service execution with the result of another service execution. This is a good fit for the command pattern. However, it would be great to be able to execute services in parallel whenever possible. Creating a reactive application service layer makes a robust and efficient solution for business logic implementation.

Other Solutions

Already mentioned NetflixObservableCommand can be used to implement reactive services.

Why itzap-rxjava

itzaprxjava provides and easy way to turn any executable code into RxJava observable. For example

public Observable<Pair[]> read() { return new RunnableCommand<Pair[]>("cmd-read-flatFile") { @Override protected Pair[] run() { // do some processing return new Pair[0]; } }.toObservable(); }
Code language: PHP (php)

In this example, file processing code is wrapped into RxJava Observable with ease. In addition, itzap-rxjava will provide logging statements that would measure execution time.

Code

Vision Statement

This project was inspired by the requirement of loading Java dependencies at run time with the ability to dynamically switch dependency versions without application re-start. This can be useful for a Java library that can be downloaded and invoked while older versions of the library still present and can be used.

Use Case

Let’s imagine a front end application that invokes back end Java library for processing and needs to be able to switch between processor versions without application re-start. Moreover, this application will be able to download new versions of the processor library and make it available without any re-installer. In addition, you need iterating over its design or implement features that a group of your customers is interested in. Some of the features may affect results of the processing that are not desirable for the other group of customers. Your product can offer a solution that can satisfy both groups by providing in-process switch for the version of your processing engine. In addition, the other group of customers can try-out new version of the engine and have some time to adopt to new results.

Other Solutions

Although, I think that the itzap-proxy is great there are other solutions that can implement this requirement, or get close to it.

JBoss Modules

Jboss Modules http://jboss-modules.github.io/jboss-modules/manual/ can be used to implement this requirement. They provide an isolated class loader that can be used to load different versions of the processing engine Java back end.

Java CGI

Java CGI is originally intended for this kind of scenarios. https://www.javaworld.com/article/2076863/write-cgi-programs-in-java.html

Why itzap-proxy

The main advantage of the itzap-proxy is the ease of use. While JBoss modules can solve the problem of class loading isolation, they impose some requirement on how modules have to be defined. To effectively use JBoss Modules Java engine has to be originally designed with JBoss modules in mind which makes it impossible to retrofit older versions of the Java engine that customers may still want to use with your application. itzap-proxy does not impose any changes on the Java library that needs to be loaded at run time, which makes it a perfect solution in situations when older versions of the engine still need to be used, while new versions are being rolled out. Java CGI, on the other hand, is hard. There are frameworks that make it easier to use, but it makes the application more heavyweight.

Other Applications

Isolated Class Loader

itzap-proxy provides isolated class loader that can be used to load dependencies. If there are conflicting versions of libraries used by dependencies that cannot be reconciled by maven excludes itzap-proxy provides a way to load them using dedicated class loader. Most common examples are log4j or Apache POI used in the third party libraries.

Mixing Java 7 and Java 8

Suppose, your application provides functionality that targets different user groups, and one group cannot upgrade JVM to Java 8 while another group needs features of Java 8. Using itzap-proxy you can combine Java 7 application with Java 8 dependencies that are loaded at run time by the “Java 8 user group”. This way, you can still maintain single application with features supported by Java 7 and Java 8.

Usage

itzap-proxy

Load java jar files dynamically in isolated class loader to avoid dependency conflicts and enable modular updates. All jar files in the [main jar folder]/lib/myLib/2.0/*.jar will be loaded.

Visit my ITZap blog to read more about this project.

Code Examples

  1. Create Object from a JAR file located in the myLib/2.0 folder:
ProxyCallerInterface object = ObjectBuilder.builder()
       .setPackageName("org.my.package")
       .setClassName("MyClass")
       .setVersionInfo(newVersionInfo("myLib", "2.0"))
       .build();
object.call("objectMethod");
  1. Create object from factory method and pass "string param" and 1 to it:
ProxyCallerInterface object = ObjectBuilder.builder()
       .setPackageName("org.my.package")
       .setClassName("MyClass")
       .setVersionInfo(newVersionInfo("myLib", "2.0"))
       .setFactoryMethod("initMyClass")
       .setParams("string param", 1)
       .build();
  1. Object builder can implement proxy interface:
ProxyCallerInterface object = ObjectBuilder.builder()
   .setClassName("org.mypackage.myclass")
   .setVersionInfo(newVersionInfo("myLib", "2.0"))
   .build();

// Example with call back
object.call("addListener", ObjectBuilder.from(builder)
                            .setInterfaceName("org.mypackage.MyListener")
                            .setHandler(new InvocationHandler() {
                                @Override
                                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                   // handle call back
                                   return null;
                                })
                            .build()
  1. Instantiating object with constructor parameters
ProxyCallerInterface object = ObjectBuilder.builder()
       .setPackageName("org.my.package")
       .setClassName("MyClass")
       .setVersionInfo(newVersionInfo("myLib", "2.0"))
       .setParams("String param", 1)
       .build();
       
  1. Loading Enums
Map<String, ProxyEnum> enum = ObjectBuilder.from(builder)
                                    .setClassName("MyClass$USStates")
                                    .buildEnum();
  1. Calling setters on a created object
List<MethodDesriptor> descriptors = Lists.newArrayList();
ObjectBuilder builder = ObjectBuilder.builder()
                .setClassName("org.mypackage.MyClass")
                .setParams("String param")
                .setDescriptors(descriptors);
// call setter
descriptors.add(MethodDesriptor.method("setProperty", "property name", "value"));
ProxyCallerInterface object = builder.build();
  1. Calling static methods
List<MethodDesriptor> settings = Lists.newArrayList();
ObjectBuilder builder = ObjectBuilder.from(builder)
                .setClassName("org.MyUtils")
                .setStaticObject(true)
                .setDescriptors(settings);
                
settings.add(MethodDesriptor.method("convert", "from this string"));
builder.build();
 

I will try to summarize years of experience in some coding techniques and libraries that I have developed over the years.