Category: frameworks

Scenario

Spring framework provides mechanism for binding controller method parameters to custom model. Here is a very good article on how to implement custom data binding https://www.baeldung.com/spring-mvc-custom-data-binder During data binding there might be a need to access HTTP request body.

Solution

HTTP request body is hidden in the ModelAndViewContainer object that spring provides to the argument resolver. Here is a code example:

public class MyContextArgumentResolver implements HandlerMethodArgumentResolver {
  @Override
  public boolean supportsParameter(@Nullable MethodParameter methodParameter) {
    return methodParameter != null &&
        methodParameter.getParameterAnnotation(MyContext.class) != null;
  }

  @Override
  public Object resolveArgument(@Nullable MethodParameter methodParameter,
                                ModelAndViewContainer modelAndViewContainer,
                                @Nullable NativeWebRequest nativeWebRequest,

    return toContext(modelAndViewContainer);
  }
                                
  private MyContext toContext(ModelAndViewContainer modelAndViewContainer) {
    if (modelAndViewContainer == null) {
      return MyContext.builder().build();
    }

    MyContext.Builder builder = MyContext.builder();

    modelAndViewContainer.getModel().forEach((k, v) -> {
      if (v instanceof BindingResult) {
        BindingResult result = (BindingResult) v;
        if (result.getTarget() instanceof Map) {
          // plain map
          builder
            .setPropertyOne(((Map<?, ?>) result.getTarget()).get("propertyOne"))
            .setPropertyTwo(((Map<?, ?>) result.getTarget()).get("propertyTwo"));         
        } else if (result.getTarget() instanceof MyRequest) {
          // handle MyRequest request
		  builder
            .setPropertyOne(((MyRequest) result.getTarget()).getpropertyOne())
            .setPropertyTwo(((MyRequest) result.getTarget()).getpropertyTwo());
        }
      }
    });

    return builder.builder();
  }                                
}

All details about setting up method argument resolver are omitted and the focus is on the toContext function that demonstrates how to get request body from ModelAndViewContainer.

Scenario

spring-cloud/spring-boot application is using zuul API gateway to route client requests. Here is zuul config snippet

zuul.ribbonIsolationStrategy=THREAD
zuul.threadPool.useSeparateThreadPools=true

ribbon.eureka.enabled = false

# sevices
...

Invoking service APIs through zuul gateway resulted in HTTP 500 response that looked something like this:

{
  "timestamp": "2019-12-18T21:37:10.168+0000",
  "status": 500,
  "error": "Internal Server Error",
  "message": "REJECTED_THREAD_EXECUTION"
}

Interesting part of this response is REJECTED_THREAD_EXECUTION. None of the services were reporting this message. After looking deeper, the Zuul log showed this error

Caused by: java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@3b17817e rejected from java.util.concurrent.ThreadPoolExecutor@67f89c35[Running, pool size = 10, active threads = 10, queued tasks = 0, completed tasks = 2535368]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) ~[?:1.8.0_212]
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) ~[?:1.8.0_212]

Solution

After realizing that exception is in the zuul log it was a matter of finding the right properties to configure services thread pools. By default, zuul was using 10 threads with the max thread count set to 10. To change this default across all services set these properties:

# defualt threadpool config
hystrix.threadpool.default.coreSize = 150
hystrix.threadpool.default.maximumSize = 150

It is also possible to configure thread pools for each service that zuul is routing to. For example:

zuul.routes.first.url=firstServiceId
zuul.routes.second.url=secondServiceId

hystrix.threadpool.firstServiceId.coreSize = 150
hystrix.threadpool.firstServiceId.maximumSize = 150

hystrix.threadpool.secondServiceId.coreSize = 50
hystrix.threadpool.secondServiceId.maximumSize = 150

Scenario

Suppose you are working on multiple Spring Boot/Spring Cloud projects that are using the spring cloud config server. You setup property encryption for the spring-cloud config. The encryption key is sored in your local profile as an environment variable ENCRYPT_KEY. Then, you switch to work on another spring-boot/spring-cloud project that does not use spring config server and have spring-cloud-context as a dependency. When you run this project you get the following exception stack trace:

Caused by: java.lang.NullPointerException: null
	at org.springframework.cloud.context.encrypt.EncryptorFactory.create(EncryptorFactory.java:54)
	at org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration$RsaEncryptionConfiguration.textEncryptor(EncryptionBootstrapConfiguration.java:83)
	at org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration$RsaEncryptionConfiguration$$EnhancerBySpringCGLIB$$c1a972bf.CGLIB$textEncryptor$0(<generated>)
	at org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration$RsaEncryptionConfiguration$$EnhancerBySpringCGLIB$$c1a972bf$$FastClassBySpringCGLIB$$4d33eb7b.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363)
	at org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration$RsaEncryptionConfiguration$$EnhancerBySpringCGLIB$$c1a972bf.textEncryptor(<generated>)
	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.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
	... 31 common frames omitted

Diagnostic

This is actually a spring bug! Another way to provide the encryption key to the config server is to use -Dencrypt.key=123 system property. If you do it this way everything is working fine. The problem only arises when the environment variable ENCRYPT_KEY is used. The root cause is in:

org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration.KeyCondition

This condition bean determines whether spring will initialize TextEncryptor class or not. When spring detects ENCRYPT_KEY environment variable the TextEncryptor class gets created. It uses KeyProperties to initialize encryptor. If -Dencrypt.key=123 is provided then KeyProperties is initialized with the key provided as a system property. If ENCRYPT_KEY environment variable is found KeyProperties are not initialized and the key member is null.

Solution

Remove ENCRYPT_KEY property from the environment prior to running spring boot application. The problem is fixed.

Vision Statement

Provide a library that can serve swagger UI in Jersey application. Inspiration for this library was the SpringFox Swagger UI project that works with spring boot application.

Use Case

Suppose you have a runnable Jar Jersey REST API application with Open API annotations and would like to provide a swagger UI for API consumers to learn and play with your APIs. Swagger UI comes as a set of static Javascript files that need to be included in your application. In addition, you need to configure a web context that will serve swagger UI pages. By including itzap-jerseyswagger.jar in your project you will get an endpoint that will load functional Swagger UI with your API definitions.

Implementation Details

Here is the code that starts embedded Tomcat in the project that I described in my previous post itzap-message. I modified it to use Open API annotations and include swagger UI.

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");

        SwaggerContext.addSwaggerServlet(tomcat, context,
                ConfigBuilder.builder(ConfigType.TYPE_SAFE)
                        .build()
                        .getConfig("swagger"),
                EmailApplication.class);

        tomcat.getConnector();
        tomcat.start();
        tomcat.getServer().await();
    }

The code to notice here is:

SwaggerContext.addSwaggerServlet(tomcat, context,
                ConfigBuilder.builder(ConfigType.TYPE_SAFE)
                        .build()
                        .getConfig("swagger"),
                EmailApplication.class);

That all you need to do to add swagger UI to the REST API application.

There are a few properties that need to be added to the project configuration.

swagger.package="com.itzap"
swagger.apiBaseUrl="http://${email.host}/v1/itzap/message/"

Now you can start the project java -jar message-impl/target/message-impl-0.0.1-SNAPSHOT.jar and navigate your browser to

http://localhost:8025/api/v1/swagger

Readme

itzap-jerseyswagger

itzap-jerseyswagger library is designed to provide swagger UI endpoint to the Jersey REST API application.

Usage

Include itzap-jerseyswagger dependency to your project.

<dependency>
    <groupId>com.itzap</groupId>
    <artifactId>itzap-jerseyswagger</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

add the following properties to the application config file

swagger.package="com.itzap"
swagger.apiBaseUrl="http://${email.host}/v1/itzap/message/"

The following code will add swaggerUI artifacts and context

SwaggerContext.addSwaggerServlet(tomcat, context,
        ConfigBuilder.builder(ConfigType.TYPE_SAFE)
                .build()
                .getConfig("swagger"),
        EmailApplication.class);

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
  • Build all projects
    • cd itzap-parent && mvn clean install
    • cd ../itzap-common && mvn clean install

Visit my ITZap blog to read more about this project.

Code

Complete implementation can be found here:

Scenario

In a spring-boot application that uses spring-boot-dependencies instead of spring-boot-starter-parent and a third party library that depends on elasticsearch version that is not comparable with version 6.x, elasticsearch dependency version get mixed up creating class loading issue.

Steps to reproduce

Create a spring-boot project and include spring-boot-dependencies in the dependency management section of the maven pom.xml

<properties>
  <spring.boot.version>2.1.6.RELEASE</spring.boot.version>
  <spring.cloud.version>Greenwich.RELEASE</spring.cloud.version>
</properties>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>${spring.boot.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>${spring.cloud.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

Later, in the dependencies section include a third party library that depends on elastic search version 5.6.8

<dependnecies>
  <dependency>
    <groupId>com.netflix.conductor</groupId>
    <artifactId>conductor-es5-persistence</artifactId>
    <version>2.8.1</version>
  </dependency>
</dependnecies>

Note that conductor-es5-persistence depends on elasticsearch version 5.6.8

The try to run mvn dependency:tree on your project and observe the following:

[INFO] +- com.netflix.conductor:conductor-es5-persistence:jar:2.8.1:compile
[INFO] |  +- org.elasticsearch:elasticsearch:jar:6.4.3:compile
[INFO] |  |  +- org.elasticsearch:elasticsearch-core:jar:6.4.3:compile

Where 6.4.3 is coming from?

Diagnostic

Turns out mvn dependency:tree shows the issue, but it does not say where org.elasticsearch:elasticsearch:jar:6.4.3:compile is comming from. The best command for the job is:

mvn -Dverbose=true help:effective-pom

This command outputs effective POM that can be redirected to a file mvn -Dverbose=true help:effective-pom > ~/pom.xml and analysed. The effective pom will look something like this

...
<dependency>
  <groupId>org.elasticsearch</groupId>  <!-- org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE, line 1965 -->
  <artifactId>elasticsearch</artifactId>  <!-- org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE, line 1966 -->
  <version>6.4.3</version>  <!-- org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE, line 1967 -->
</dependency>
...

Magic! now we can see exactly where the problem is:

<!-- org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE, line 1967 -->

Solution

Lets look at this import dependency:

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>${spring.boot.version}</version>
    <type>pom</type>
    <scope>import</scope>
  </dependency>

spring-boot-dependencies is imported, so there is no easy way to exclude elasticsearch dependency that it pulls into our project. A simple maven <exclude> will not work here. The only way to solve the issue is to bring elasticsearch dependencies into your project dependency management block like this:

<properties>
  <spring.boot.version>2.1.6.RELEASE</spring.boot.version>
  <spring.cloud.version>Greenwich.RELEASE</spring.cloud.version>
  
  <elasticsearch.version>5.6.8</elasticsearch.version>
</properties>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>${spring.boot.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>${spring.cloud.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
    
    <!-- Elasticsearch -->
    <dependency>
      <groupId>org.elasticsearch</groupId>
      <artifactId>elasticsearch</artifactId>
      <version>${elasticsearch.version}</version>
    </dependency>

    <dependency>
      <groupId>org.elasticsearch.client</groupId>
      <artifactId>transport</artifactId>
      <version>${elasticsearch.version}</version>
    </dependency>

    <dependency>
      <groupId>org.elasticsearch.distribution.integ-test-zip</groupId>
      <artifactId>elasticsearch</artifactId>
      <version>${elasticsearch.version}</version>
      <type>zip</type>
    </dependency>

    <dependency>
      <groupId>org.elasticsearch.plugin</groupId>
      <artifactId>transport-netty4-client</artifactId>
      <version>${elasticsearch.version}</version>
    </dependency>

  </dependencies>
</dependencyManagement>

Now run mvn -Dverbose=true help:effective-pom and confirm that the problem is fixed.

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