Category: Pattern

Vision Statement

Immutable POJOs are key to bug-free programs. It is not straight forward to write immutable POJOs in Java. Builder pattern comes handy to solve this problem by separating getters and setters. The problem with the Builder pattern in java is the need to duplicate POJO properties. The purpose of this solution is to minimize the downside of the Builder pattern in java by separating the state into a separate class.

Use Case

Suppose we have a User POJO like this:

public class User {
    private final String lastName;
  	private final String firstName;

    private User(Builder builder) {
        this.lastName = builder.lastName;
      	this.firstName = builder.firstName;
    }

    public String getLastName() {
        return this.lastName;
    }

    public String getFirstName() {
        return this.firstName;
    }

    public static class Builder {
		private String lastName;
  		private String firstName;
      
        public Builder setLastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public Builder setFirstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }

    public static Builder builder() {
        return new Builder();
    }
}

The problem with the above solution is that lastName and firstName attributes need to be repeated for both builder and POJO itself.

Solution

To address this issue we can try to separate attributes into a separate State class.

public class User {
  	private static class State {
    	private String lastName;
  		private String firstName;
    }
	
  	private final State state;
  
    private User(Builder builder) {
        this.state = builder.state;
    }

    public String getLastName() {
        return this.state.lastName;
    }

    public String getFirstName() {
        return this.state.firstName;
    }

    public static class Builder {
		private final State state = new State();
      
        public Builder setLastName(String lastName) {
            this.state.lastName = lastName;
            return this;
        }

        public Builder setFirstName(String firstName) {
            this.state.firstName = firstName;
            return this;
        }
      
        public User build() {
            return new User(this);
        }
      
      	public static Builder from(User user) {
          return User.builder()
            .setLastName(user.getLastName())
            .setFirstName(user.getFirstName());
        }
    }

    public static Builder builder() {
        return new Builder();
    }
}

Bonus for IntelliJ Users

IntelliJ’s generate Getter/Setter feature cannot be used effectively when attributes of the POJO are factored out into a separate State. Luckily, IntelliJ provides the ability to customize generators. Here is an example of a possible custom Getter/Setter generator scripts.

Getter

#if($field.modifierStatic)
static ##
#end
$field.type ##
#set($name = $StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($helper.getPropertyName($field, $project))))
#if ($field.boolean && $field.primitive)
  is##
#else
  get##
#end
${name}() {
  return this.state.$field.name;
}

Setter

#set($paramName = $helper.getParamName($field, $project))
public ##
#if($field.modifierStatic)
static void ##
#else
  Builder ##
#end
set$StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($helper.getPropertyName($field, $project)))($field.type $paramName) {
#if ($field.name == $paramName)
  #if (!$field.modifierStatic)
  this.state.##
  #else
    $this.state.##
  #end
#end
$field.name = $paramName;
#if(!$field.modifierStatic)
return this;
#end
}

Navigate to the State class and generate Getters/Setters. Then copy getters to the POJO and setters to the builder. A small extra step but save a lot of boilerplate code.

Vision Statement

Build 7.3.x elasticsearch embedded server into itzapelasticsearch library to enable easy prototyping and unit/integration testing

How It Works

Elasticsearch embedded server starts a single elasticsearch Node. Elasticsearch server node will be listening for incoming requests on port 9200 Elasticsearch client can be configured to access embedded server using the following url: http://localhost:9200 Elasticsearch embedded server will create data and home temp folders that will be deleted opon application close.

Code

Here is the main class that implements elasticsearch embedded server node

public class EmbeddedElasticSearchServer implements IOData {
  private static final Logger LOGGER = LoggerFactory.getLogger(EmbeddedElasticSearchServer.class);

  private Node instance;
  private int port;
  private final AnyConfig config;

  public EmbeddedElasticSearchServer(AnyConfig config) {
    this.config = config;
  }

  private static class PluginConfigurableNode extends Node {
    PluginConfigurableNode(Settings input,
                           Map<String, String> properties,
                           Path configPath,
                           Supplier<String> defaultNodeName,
                           Collection<Class<? extends Plugin>> classpathPlugins) {
      super(InternalSettingsPreparer.prepareEnvironment(input, properties, configPath, defaultNodeName), classpathPlugins, false);
    }
  }

  @Override
  public synchronized Completable start() {
    return new RunnableCommand<Void>("cmd-start") {
      @Override
      protected Void run() {
        Settings settings = getSettings();

        instance = new PluginConfigurableNode(settings, ImmutableMap.of(),
                                              null, () -> config.getString(EsConfig.CLUSTER_NAME),
                                              singletonList(Netty4Plugin.class));
        try {
          instance.start();
          Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            try {
              if (instance != null) {
                instance.close();
              }
            } catch (IOException e) {
              LOGGER.error("Error closing ElasticSearch");
            }
          }));
          LOGGER.info("ElasticSearch cluster {} started in local mode on port {}", instance.settings().get("cluster.name"),
                      port);
          return null;
        } catch (NodeValidationException e) {
          throw new IZapException("Failed to start embedded elastic search server", e);
        }
      }
    }.toCompletable();
  }

  @Override
  public synchronized Completable stop() {
    return new RunnableCommand<Void>("cmd-stop") {
      @Override
      protected Void run() {
        if (instance != null && !instance.isClosed()) {
          LOGGER.info("Stopping Elastic Search");
          try {
            instance.close();
            instance = null;
            LOGGER.info("Elastic Search on port {} stopped", port);
          } catch (IOException e) {
            throw new IZapException("Failed to close elastic search embedded server", e);
          }
        }

        return null;
      }
    }.toCompletable();
  }

  private Settings getSettings() {
    String clusterName = config.getString(EsConfig.CLUSTER_NAME);
    String host = config.getString(EsConfig.HOST);
    port = config.getInt(EsConfig.PORT);

    try {
      File dataDir = Files.createTempDirectory(clusterName + "_" + System.currentTimeMillis() + "data").toFile();
      FileUtils.forceDeleteOnExit(dataDir);
      cleanDataDir(dataDir.getAbsolutePath());

      File homeDir = Files.createTempDirectory(clusterName + "_" + System.currentTimeMillis() + "-home").toFile();
      cleanDataDir(homeDir.getAbsolutePath());
      FileUtils.forceDeleteOnExit(homeDir);

      Settings.Builder settingsBuilder = Settings.builder()
        .put("cluster.name", clusterName)
        .put("http.host", host)
        .put("http.port", port)
        .put("transport.tcp.port", port + 100)
        .put(EsConfig.DATA_PATH.getName(), dataDir.getAbsolutePath())
        .put(EsConfig.HOME_PATH.getName(), homeDir.getAbsolutePath())
        .put("http.cors.enabled", true)
        .put("node.data", true)
        .put("http.type", "netty4")
        .put("transport.type", "netty4");

      return settingsBuilder.build();
    } catch (IOException e) {
      throw new IZapException("Failed to create temp data/home dir.", e);
    }
  }
}

Elasticsearch embedded server implementation is using RunnableCommand command pattern to implement start and stopmethods. Please note that RunnableCommand returns Completable and not Observable. Sice Observable<Void> is no longer permitted in RxJava2 and start/stop methods have no return values. Here is the unit test for the elasticsearch embedded server

public class EmbeddedElasticSearchServerTest {
    private EmbeddedElasticSearchServer server;

    @Before
    public void setup() {
        AnyConfig config = ConfigBuilder.builder(ConfigType.TYPE_SAFE)
                .setFileName(this.getClass()
                        .getResource("/es-config.properties").getFile())
                .build();

        server = new EmbeddedElasticSearchServer(config);
    }

    @Test
    public void startTest() {
        server.start().blockingGet();
        server.stop().blockingGet();
    }
}

Elasticsearch dependencies

<dependency>
  <groupId>org.elasticsearch.client</groupId>
  <artifactId>elasticsearch-rest-high-level-client</artifactId>
  <version>7.3.1</version>
</dependency>

<dependency>
  <groupId>org.elasticsearch</groupId>
  <artifactId>elasticsearch</artifactId>
  <version>7.3.1</version>
</dependency>

<dependency>
  <groupId>org.elasticsearch.client</groupId>
  <artifactId>transport</artifactId>
  <version>7.3.1</version>
</dependency>

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