Repository: moquette-io/moquette
Pull request #761 (2023-07-21 10:21:16)

Create a DSL to ease the creation of a Server instance
## What does this PR do?
Create a fluent class (`FluentConfig`) to instantiate, configure and run the server in one liner.
Instead of creating an instance of `IConfig` and invoking repeatedly put methods with some constants that has to be fetched from a pool (`BrokerConstants`) like in:
```java
final Properties configProps =
configProps.put(BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME, Integer.toString(BrokerConstants.WEBSOCKET_PORT));
configProps.put(BrokerConstants.DATA_PATH_PROPERTY_NAME, dbPath);
configProps.put(BrokerConstants.PERSISTENCE_ENABLED_PROPERTY_NAME, "true");
IConfig config = new MemoryConfig(configProps);
Server server = new Server();
server.startServer(config);
```

It becomes more expressive like 
```java
 Server server = new Server()
            .withConfig()
            .dataPath(dbPath)
            .enablePersistence()
            .disableTelemetry()
            .websocketPort(BrokerConstants.WEBSOCKET_PORT)
            .startServer();
```

Moved configuration related BrokerConstants into the more expressive IConfig class

## Why is it important/What is the impact to the user?
Implementation of a DSL to ease the configuration of the Server without boring use of Properties and BrokerConstants. This simplify the embedding of the Server in other projects.


andsel: @hylkevds may I ask your thoughts and review of this PR, please?
hylkevds: > @hylkevds may I ask your thoughts and review of this PR, please?

Sure, I'll look over it this weekend.

Patch:
diff --git a/ChangeLog.txt b/ChangeLog.txt
index 7d747c66..5feb4ecb 100644
--- a/ChangeLog.txt
+++ b/ChangeLog.txt
@@ -1,4 +1,5 @@
 Version 0.17-SNAPSHOT:
+   [feature] Introduced DSL FluentConfig to simplify Server's API instantiation #761.
    [fix] resolved issue #633 of bad perfomance when adding many subscriptions to few topics, resolved in #758.
    [fix] resolved issue #629 that originated from subscription trees wide and flat, resolved in #630
    [dependency] updated Netty to 4.1.93 and tcnative to 2.0.61 (#755)
diff --git a/broker/src/main/java/io/moquette/BrokerConstants.java b/broker/src/main/java/io/moquette/BrokerConstants.java
index 4b63aa9c..d13440d6 100644
--- a/broker/src/main/java/io/moquette/BrokerConstants.java
+++ b/broker/src/main/java/io/moquette/BrokerConstants.java
@@ -16,6 +16,8 @@
 
 package io.moquette;
 
+import io.moquette.broker.config.IConfig;
+
 import java.io.File;
 
 public final class BrokerConstants {
@@ -29,45 +31,58 @@ public final class BrokerConstants {
      * */
     @Deprecated
     public static final String PERSISTENT_STORE_PROPERTY_NAME = "persistent_store";
-    public static final String DATA_PATH_PROPERTY_NAME = "data_path";
-    public static final String PERSISTENT_QUEUE_TYPE_PROPERTY_NAME = "persistent_queue_type"; // h2 or segmented, default h2
-    public static final String PERSISTENCE_ENABLED_PROPERTY_NAME = "persistence_enabled"; // true or false, default true
+    @Deprecated
+    public static final String DATA_PATH_PROPERTY_NAME = IConfig.DATA_PATH_PROPERTY_NAME;
+    @Deprecated
+    public static final String PERSISTENT_QUEUE_TYPE_PROPERTY_NAME = IConfig.PERSISTENT_QUEUE_TYPE_PROPERTY_NAME;
+    @Deprecated
+    public static final String PERSISTENCE_ENABLED_PROPERTY_NAME = IConfig.PERSISTENCE_ENABLED_PROPERTY_NAME;
     public static final String SEGMENTED_QUEUE_PAGE_SIZE = "queue_page_size";
     public static final int MB = 1024 * 1024;
     public static final int DEFAULT_SEGMENTED_QUEUE_PAGE_SIZE = 64 * MB;
     public static final String SEGMENTED_QUEUE_SEGMENT_SIZE = "queue_segment_size";
     public static final int DEFAULT_SEGMENTED_QUEUE_SEGMENT_SIZE = 4 * MB;
     public static final String AUTOSAVE_INTERVAL_PROPERTY_NAME = "autosave_interval";
-    public static final String PASSWORD_FILE_PROPERTY_NAME = "password_file";
-    public static final String PORT_PROPERTY_NAME = "port";
-    public static final String HOST_PROPERTY_NAME = "host";
+    @Deprecated
+    public static final String PASSWORD_FILE_PROPERTY_NAME = IConfig.PASSWORD_FILE_PROPERTY_NAME;
+    @Deprecated // use IConfig.PORT_PROPERTY_NAME
+    public static final String PORT_PROPERTY_NAME = IConfig.PORT_PROPERTY_NAME;
+    @Deprecated
+    public static final String HOST_PROPERTY_NAME = IConfig.HOST_PROPERTY_NAME;
     public static final String DEFAULT_MOQUETTE_STORE_H2_DB_FILENAME = "moquette_store.h2";
     public static final String DEFAULT_PERSISTENT_PATH = System.getProperty("user.dir") + File.separator
             + DEFAULT_MOQUETTE_STORE_H2_DB_FILENAME;
-    public static final String WEB_SOCKET_PORT_PROPERTY_NAME = "websocket_port";
-    public static final String WSS_PORT_PROPERTY_NAME = "secure_websocket_port";
-    public static final String WEB_SOCKET_PATH_PROPERTY_NAME = "websocket_path";
+    @Deprecated
+    public static final String WEB_SOCKET_PORT_PROPERTY_NAME = IConfig.WEB_SOCKET_PORT_PROPERTY_NAME;
+    @Deprecated
+    public static final String WSS_PORT_PROPERTY_NAME = IConfig.WSS_PORT_PROPERTY_NAME;
+    @Deprecated
+    public static final String WEB_SOCKET_PATH_PROPERTY_NAME = IConfig.WEB_SOCKET_PATH_PROPERTY_NAME;
     public static final String WEB_SOCKET_MAX_FRAME_SIZE_PROPERTY_NAME = "websocket_max_frame_size";
-    public static final String SESSION_QUEUE_SIZE = "session_queue_size";
-
-    /**
-     * Defines the SSL implementation to use, default to "JDK".
-     * @see io.netty.handler.ssl.SslProvider#name()
-     */
-    public static final String SSL_PROVIDER = "ssl_provider";
-    public static final String SSL_PORT_PROPERTY_NAME = "ssl_port";
-    public static final String JKS_PATH_PROPERTY_NAME = "jks_path";
-
-    /** @see java.security.KeyStore#getInstance(String) for allowed types, default to "jks" */
-    public static final String KEY_STORE_TYPE = "key_store_type";
-    public static final String KEY_STORE_PASSWORD_PROPERTY_NAME = "key_store_password";
-    public static final String KEY_MANAGER_PASSWORD_PROPERTY_NAME = "key_manager_password";
-    public static final String ALLOW_ANONYMOUS_PROPERTY_NAME = "allow_anonymous";
+    @Deprecated
+    public static final String SESSION_QUEUE_SIZE = IConfig.SESSION_QUEUE_SIZE;
+    @Deprecated
+    public static final String SSL_PROVIDER = IConfig.SSL_PROVIDER;
+    @Deprecated
+    public static final String SSL_PORT_PROPERTY_NAME = IConfig.SSL_PORT_PROPERTY_NAME;
+    @Deprecated
+    public static final String JKS_PATH_PROPERTY_NAME = IConfig.JKS_PATH_PROPERTY_NAME;
+    @Deprecated
+    public static final String KEY_STORE_TYPE = IConfig.KEY_STORE_TYPE;
+    @Deprecated
+    public static final String KEY_STORE_PASSWORD_PROPERTY_NAME = IConfig.KEY_STORE_PASSWORD_PROPERTY_NAME;
+    @Deprecated
+    public static final String KEY_MANAGER_PASSWORD_PROPERTY_NAME = IConfig.KEY_MANAGER_PASSWORD_PROPERTY_NAME;
+    @Deprecated
+    public static final String ALLOW_ANONYMOUS_PROPERTY_NAME = IConfig.ALLOW_ANONYMOUS_PROPERTY_NAME;
     public static final String REAUTHORIZE_SUBSCRIPTIONS_ON_CONNECT = "reauthorize_subscriptions_on_connect";
     public static final String ALLOW_ZERO_BYTE_CLIENT_ID_PROPERTY_NAME = "allow_zero_byte_client_id";
-    public static final String ACL_FILE_PROPERTY_NAME = "acl_file";
-    public static final String AUTHORIZATOR_CLASS_NAME = "authorizator_class";
-    public static final String AUTHENTICATOR_CLASS_NAME = "authenticator_class";
+    @Deprecated
+    public static final String ACL_FILE_PROPERTY_NAME = IConfig.ACL_FILE_PROPERTY_NAME;
+    @Deprecated
+    public static final String AUTHORIZATOR_CLASS_NAME = IConfig.AUTHORIZATOR_CLASS_NAME;
+    @Deprecated
+    public static final String AUTHENTICATOR_CLASS_NAME = IConfig.AUTHENTICATOR_CLASS_NAME;
     public static final String DB_AUTHENTICATOR_DRIVER = "authenticator.db.driver";
     public static final String DB_AUTHENTICATOR_URL = "authenticator.db.url";
     public static final String DB_AUTHENTICATOR_QUERY = "authenticator.db.query";
@@ -84,19 +99,17 @@ public final class BrokerConstants {
     public static final String NETTY_SO_KEEPALIVE_PROPERTY_NAME = "netty.so_keepalive";
     public static final String NETTY_CHANNEL_TIMEOUT_SECONDS_PROPERTY_NAME = "netty.channel_timeout.seconds";
     public static final String NETTY_EPOLL_PROPERTY_NAME = "netty.epoll";
-    public static final String NETTY_MAX_BYTES_PROPERTY_NAME = "netty.mqtt.message_size";
-    public static final int DEFAULT_NETTY_MAX_BYTES_IN_MESSAGE = 8092;
+    @Deprecated
+    public static final String NETTY_MAX_BYTES_PROPERTY_NAME = IConfig.NETTY_MAX_BYTES_PROPERTY_NAME;
+    @Deprecated
+    public static final int DEFAULT_NETTY_MAX_BYTES_IN_MESSAGE = IConfig.DEFAULT_NETTY_MAX_BYTES_IN_MESSAGE;
     /**
      * @deprecated use the BUFFER_FLUSH_MS_PROPERTY_NAME
      * */
     @Deprecated
     public static final String IMMEDIATE_BUFFER_FLUSH_PROPERTY_NAME = "immediate_buffer_flush";
-    /**
-     * 0/immediate means immediate flush, like immediate_buffer_flush = true
-     * -1/full means no explicit flush, let Netty flush when write buffers are full, like immediate_buffer_flush = false
-     * a number of milliseconds to between flushes
-     * */
-    public static final String BUFFER_FLUSH_MS_PROPERTY_NAME = "buffer_flush_millis";
+    @Deprecated
+    public static final String BUFFER_FLUSH_MS_PROPERTY_NAME = IConfig.BUFFER_FLUSH_MS_PROPERTY_NAME;
     public static final int NO_BUFFER_FLUSH = -1;
     public static final int IMMEDIATE_BUFFER_FLUSH = 0;
 
@@ -105,14 +118,16 @@ public final class BrokerConstants {
     public static final String METRICS_LIBRATO_TOKEN_PROPERTY_NAME = "metrics.librato.token";
     public static final String METRICS_LIBRATO_SOURCE_PROPERTY_NAME = "metrics.librato.source";
 
-    public static final String ENABLE_TELEMETRY_NAME = "telemetry_enabled";
+    @Deprecated
+    public static final String ENABLE_TELEMETRY_NAME = IConfig.ENABLE_TELEMETRY_NAME;
 
     public static final String BUGSNAG_ENABLE_PROPERTY_NAME = "use_bugsnag";
     public static final String BUGSNAG_TOKEN_PROPERTY_NAME = "bugsnag.token";
 
     public static final String STORAGE_CLASS_NAME = "storage_class";
 
-    public static final String PERSISTENT_CLEAN_EXPIRATION_PROPERTY_NAME = "persistent_client_expiration";
+    @Deprecated
+    public static final String PERSISTENT_CLIENT_EXPIRATION_PROPERTY_NAME = IConfig.PERSISTENT_CLIENT_EXPIRATION_PROPERTY_NAME;
 
     public static final int FLIGHT_BEFORE_RESEND_MS = 5_000;
     public static final int INFLIGHT_WINDOW_SIZE = 10;
diff --git a/broker/src/main/java/io/moquette/broker/Server.java b/broker/src/main/java/io/moquette/broker/Server.java
index 42a2e1e9..0d1a2905 100644
--- a/broker/src/main/java/io/moquette/broker/Server.java
+++ b/broker/src/main/java/io/moquette/broker/Server.java
@@ -16,11 +16,7 @@
 package io.moquette.broker;
 
 import io.moquette.BrokerConstants;
-import io.moquette.broker.config.FileResourceLoader;
-import io.moquette.broker.config.IConfig;
-import io.moquette.broker.config.IResourceLoader;
-import io.moquette.broker.config.MemoryConfig;
-import io.moquette.broker.config.ResourceLoaderConfig;
+import io.moquette.broker.config.*;
 import io.moquette.broker.security.ACLFileParser;
 import io.moquette.broker.security.AcceptAllAuthenticator;
 import io.moquette.broker.security.DenyAllAuthorizatorPolicy;
@@ -245,8 +241,8 @@ public class Server {
         final Authorizator authorizator = new Authorizator(authorizatorPolicy);
 
         final int globalSessionExpiry;
-        if (config.getProperty(BrokerConstants.PERSISTENT_CLEAN_EXPIRATION_PROPERTY_NAME) != null) {
-            globalSessionExpiry = (int) config.durationProp(BrokerConstants.PERSISTENT_CLEAN_EXPIRATION_PROPERTY_NAME).toMillis() / 1000;
+        if (config.getProperty(BrokerConstants.PERSISTENT_CLIENT_EXPIRATION_PROPERTY_NAME) != null) {
+            globalSessionExpiry = (int) config.durationProp(BrokerConstants.PERSISTENT_CLIENT_EXPIRATION_PROPERTY_NAME).toMillis() / 1000;
         } else {
             globalSessionExpiry = INFINITE_EXPIRY;
         }
@@ -671,4 +667,8 @@ public class Server {
     public boolean disconnectAndPurgeClientState(final String clientId) {
         return sessions.dropSession(clientId, true);
     }
+
+    public FluentConfig withConfig() {
+        return new FluentConfig(this);
+    }
 }
diff --git a/broker/src/main/java/io/moquette/broker/config/FluentConfig.java b/broker/src/main/java/io/moquette/broker/config/FluentConfig.java
new file mode 100644
index 00000000..2511d9ed
--- /dev/null
+++ b/broker/src/main/java/io/moquette/broker/config/FluentConfig.java
@@ -0,0 +1,285 @@
+package io.moquette.broker.config;
+
+import io.moquette.BrokerConstants;
+import io.moquette.broker.Server;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.function.Consumer;
+
+import static io.moquette.broker.config.IConfig.ACL_FILE_PROPERTY_NAME;
+import static io.moquette.broker.config.IConfig.ALLOW_ANONYMOUS_PROPERTY_NAME;
+import static io.moquette.broker.config.IConfig.AUTHENTICATOR_CLASS_NAME;
+import static io.moquette.broker.config.IConfig.AUTHORIZATOR_CLASS_NAME;
+import static io.moquette.broker.config.IConfig.BUFFER_FLUSH_MS_PROPERTY_NAME;
+import static io.moquette.broker.config.IConfig.DATA_PATH_PROPERTY_NAME;
+import static io.moquette.broker.config.IConfig.DEFAULT_NETTY_MAX_BYTES_IN_MESSAGE;
+import static io.moquette.broker.config.IConfig.ENABLE_TELEMETRY_NAME;
+import static io.moquette.broker.config.IConfig.PORT_PROPERTY_NAME;
+import static io.moquette.broker.config.IConfig.JKS_PATH_PROPERTY_NAME;
+import static io.moquette.broker.config.IConfig.KEY_MANAGER_PASSWORD_PROPERTY_NAME;
+import static io.moquette.broker.config.IConfig.KEY_STORE_PASSWORD_PROPERTY_NAME;
+import static io.moquette.broker.config.IConfig.KEY_STORE_TYPE;
+import static io.moquette.broker.config.IConfig.NETTY_MAX_BYTES_PROPERTY_NAME;
+import static io.moquette.broker.config.IConfig.PASSWORD_FILE_PROPERTY_NAME;
+import static io.moquette.broker.config.IConfig.PERSISTENCE_ENABLED_PROPERTY_NAME;
+import static io.moquette.broker.config.IConfig.PERSISTENT_CLIENT_EXPIRATION_PROPERTY_NAME;
+import static io.moquette.broker.config.IConfig.PERSISTENT_QUEUE_TYPE_PROPERTY_NAME;
+import static io.moquette.broker.config.IConfig.HOST_PROPERTY_NAME;
+import static io.moquette.broker.config.IConfig.SESSION_QUEUE_SIZE;
+import static io.moquette.broker.config.IConfig.SSL_PORT_PROPERTY_NAME;
+import static io.moquette.broker.config.IConfig.SSL_PROVIDER;
+import static io.moquette.broker.config.IConfig.WEB_SOCKET_PORT_PROPERTY_NAME;
+
+/**
+ *  DSL to create Moquette config.
+ *  It provides methods to configure every available setting.
+ *  To be used instead of Properties instance used combined with MemoryConfig.
+ * */
+public class FluentConfig {
+
+    private Server server;
+    private TLSConfig tlsConfig;
+
+    public enum BufferFlushKind {
+        IMMEDIATE, FULL;
+    }
+
+    public enum PersistentQueueType {
+        H2, SEGMENTED;
+    }
+
+    public enum SSLProvider {
+        SSL, OPENSSL, OPENSSL_REFCNT;
+    }
+
+    public enum KeyStoreType {
+        JKS, JCEKS, PKCS12;
+    }
+
+    private enum CreationKind {
+        API, SERVER
+    }
+
+    private final Properties configAccumulator = new Properties();
+
+    private final CreationKind creationKind;
+
+    public FluentConfig() {
+        initializeDefaultValues();
+        creationKind = CreationKind.API;
+    }
+
+    // Invoked only when initialized directly by the server
+    public FluentConfig(Server server) {
+        initializeDefaultValues();
+        creationKind = CreationKind.SERVER;
+        this.server = server;
+    }
+
+    private void initializeDefaultValues() {
+        // preload with default values
+        configAccumulator.put(PORT_PROPERTY_NAME, Integer.toString(BrokerConstants.PORT));
+        configAccumulator.put(HOST_PROPERTY_NAME, BrokerConstants.HOST);
+        configAccumulator.put(PASSWORD_FILE_PROPERTY_NAME, "");
+        configAccumulator.put(ALLOW_ANONYMOUS_PROPERTY_NAME, Boolean.TRUE.toString());
+        configAccumulator.put(AUTHENTICATOR_CLASS_NAME, "");
+        configAccumulator.put(AUTHORIZATOR_CLASS_NAME, "");
+        configAccumulator.put(NETTY_MAX_BYTES_PROPERTY_NAME, String.valueOf(DEFAULT_NETTY_MAX_BYTES_IN_MESSAGE));
+        configAccumulator.put(PERSISTENT_QUEUE_TYPE_PROPERTY_NAME, PersistentQueueType.SEGMENTED.name().toLowerCase(Locale.ROOT));
+        configAccumulator.put(DATA_PATH_PROPERTY_NAME, "data/");
+        configAccumulator.put(PERSISTENCE_ENABLED_PROPERTY_NAME, Boolean.TRUE.toString());
+        configAccumulator.put(BUFFER_FLUSH_MS_PROPERTY_NAME, BufferFlushKind.IMMEDIATE.name().toLowerCase(Locale.ROOT));
+    }
+
+    public FluentConfig host(String host) {
+        configAccumulator.put(HOST_PROPERTY_NAME, host);
+        return this;
+    }
+
+    public FluentConfig port(int port) {
+        validatePort(port);
+        configAccumulator.put(PORT_PROPERTY_NAME, Integer.toString(port));
+        return this;
+    }
+
+    public FluentConfig websocketPort(int port) {
+        validatePort(port);
+        configAccumulator.put(WEB_SOCKET_PORT_PROPERTY_NAME, Integer.toString(port));
+        return this;
+    }
+
+    private static void validatePort(int port) {
+        if (port > 65_535 || port < 0) {
+            throw new IllegalArgumentException("Port must be in range [0.65535]");
+        }
+    }
+
+    public FluentConfig dataPath(String dataPath) {
+        configAccumulator.put(DATA_PATH_PROPERTY_NAME, dataPath);
+        return this;
+    }
+
+    public FluentConfig dataPath(Path dataPath) {
+        configAccumulator.put(DATA_PATH_PROPERTY_NAME, dataPath.toAbsolutePath().toString());
+        return this;
+    }
+    public FluentConfig enablePersistence() {
+        configAccumulator.put(PERSISTENCE_ENABLED_PROPERTY_NAME, "true");
+        return this;
+    }
+
+    public FluentConfig disablePersistence() {
+        configAccumulator.put(PERSISTENCE_ENABLED_PROPERTY_NAME, "false");
+        return this;
+    }
+
+    public FluentConfig persistentQueueType(PersistentQueueType type) {
+        configAccumulator.put(PERSISTENT_QUEUE_TYPE_PROPERTY_NAME, type.name().toLowerCase(Locale.ROOT));
+        return this;
+    }
+
+    public FluentConfig allowAnonymous() {
+        configAccumulator.put(ALLOW_ANONYMOUS_PROPERTY_NAME, "true");
+        return this;
+    }
+
+    public FluentConfig disallowAnonymous() {
+        configAccumulator.put(ALLOW_ANONYMOUS_PROPERTY_NAME, "false");
+        return this;
+    }
+
+    /**
+     * @param  aclPath relative path to the resource file that contains the definitions
+     * */
+    public FluentConfig aclFile(String aclPath) {
+        configAccumulator.put(ACL_FILE_PROPERTY_NAME, aclPath);
+        return this;
+    }
+
+    /**
+     * @param  passwordFilePath relative path to the resource file that contains the passwords.
+     * */
+    public FluentConfig passwordFile(String passwordFilePath) {
+        configAccumulator.put(PASSWORD_FILE_PROPERTY_NAME, passwordFilePath);
+        return this;
+    }
+    public FluentConfig bufferFlushMillis(int value) {
+        configAccumulator.put(BUFFER_FLUSH_MS_PROPERTY_NAME, Integer.valueOf(value).toString());
+        return this;
+    }
+
+    public FluentConfig bufferFlushMillis(BufferFlushKind value) {
+        configAccumulator.put(BUFFER_FLUSH_MS_PROPERTY_NAME, value.name().toLowerCase(Locale.ROOT));
+        return this;
+    }
+
+    public FluentConfig persistentClientExpiration(String expiration) {
+        configAccumulator.put(PERSISTENT_CLIENT_EXPIRATION_PROPERTY_NAME, expiration);
+        return this;
+    }
+
+    public FluentConfig sessionQueueSize(int value) {
+        configAccumulator.put(SESSION_QUEUE_SIZE, Integer.valueOf(value).toString());
+        return this;
+    }
+
+    public FluentConfig disableTelemetry() {
+        configAccumulator.put(ENABLE_TELEMETRY_NAME, "false");
+        return this;
+    }
+
+    public FluentConfig enableTelemetry() {
+        configAccumulator.put(ENABLE_TELEMETRY_NAME, "true");
+        return this;
+    }
+
+    public class TLSConfig {
+
+        private SSLProvider providerType;
+        private KeyStoreType keyStoreType;
+        private String keyStorePassword;
+        private String keyManagerPassword;
+        private String jksPath;
+        private int sslPort;
+
+        private TLSConfig() {}
+
+        public void port(int port) {
+            this.sslPort = port;
+            validatePort(port);
+        }
+
+        public void sslProvider(SSLProvider providerType) {
+            this.providerType = providerType;
+        }
+
+        public void jksPath(String jksPath) {
+            this.jksPath = jksPath;
+        }
+
+        public void jksPath(Path jksPath) {
+            jksPath(jksPath.toAbsolutePath().toString());
+        }
+
+        public void keyStoreType(KeyStoreType keyStoreType) {
+            this.keyStoreType = keyStoreType;
+        }
+
+        /**
+         * @param keyStorePassword the password to access the KeyStore
+         * */
+        public void keyStorePassword(String keyStorePassword) {
+            this.keyStorePassword = keyStorePassword;
+        }
+
+        /**
+         * @param keyManagerPassword the password to access the key manager.
+         * */
+        public void keyManagerPassword(String keyManagerPassword) {
+            this.keyManagerPassword = keyManagerPassword;
+        }
+
+        private String getSslProvider() {
+            return providerType.name().toLowerCase(Locale.ROOT);
+        }
+
+        private String getJksPath() {
+            return jksPath;
+        }
+
+        private String getKeyStoreType() {
+            return keyStoreType.name().toLowerCase(Locale.ROOT);
+        }
+    }
+
+    public FluentConfig withTLS(Consumer<TLSConfig> tlsBlock) {
+        tlsConfig = new TLSConfig();
+        tlsBlock.accept(tlsConfig);
+        configAccumulator.put(SSL_PORT_PROPERTY_NAME, Integer.toString(tlsConfig.sslPort));
+        configAccumulator.put(SSL_PROVIDER, tlsConfig.getSslProvider());
+        configAccumulator.put(JKS_PATH_PROPERTY_NAME, tlsConfig.getJksPath());
+        configAccumulator.put(KEY_STORE_TYPE, tlsConfig.getKeyStoreType());
+        configAccumulator.put(KEY_STORE_PASSWORD_PROPERTY_NAME, tlsConfig.keyStorePassword);
+        configAccumulator.put(KEY_MANAGER_PASSWORD_PROPERTY_NAME, tlsConfig.keyManagerPassword);
+
+        return this;
+    }
+
+    public IConfig build() {
+        if (creationKind != CreationKind.API) {
+            throw new IllegalStateException("Can't build a configuration started directly by the server, use startServer method instead");
+        }
+        return new MemoryConfig(configAccumulator);
+    }
+
+    public Server startServer() throws IOException {
+        if (creationKind != CreationKind.SERVER) {
+            throw new IllegalStateException("Can't start a sever from a configuration used in API mode, use build method instead");
+        }
+        server.startServer(new MemoryConfig(configAccumulator));
+        return server;
+    }
+}
diff --git a/broker/src/main/java/io/moquette/broker/config/IConfig.java b/broker/src/main/java/io/moquette/broker/config/IConfig.java
index ada8d3f6..60c973e9 100644
--- a/broker/src/main/java/io/moquette/broker/config/IConfig.java
+++ b/broker/src/main/java/io/moquette/broker/config/IConfig.java
@@ -28,6 +28,42 @@ import java.time.temporal.TemporalUnit;
 public abstract class IConfig {
 
     public static final String DEFAULT_CONFIG = "config/moquette.conf";
+    public static final String PORT_PROPERTY_NAME = "port";
+    public static final String HOST_PROPERTY_NAME = "host";
+    public static final String PASSWORD_FILE_PROPERTY_NAME = "password_file";
+    public static final String ALLOW_ANONYMOUS_PROPERTY_NAME = "allow_anonymous";
+    public static final String AUTHENTICATOR_CLASS_NAME = "authenticator_class";
+    public static final String AUTHORIZATOR_CLASS_NAME = "authorizator_class";
+    public static final String PERSISTENT_QUEUE_TYPE_PROPERTY_NAME = "persistent_queue_type"; // h2 or segmented, default h2
+    public static final String DATA_PATH_PROPERTY_NAME = "data_path";
+    public static final String PERSISTENCE_ENABLED_PROPERTY_NAME = "persistence_enabled"; // true or false, default true
+    /**
+     * 0/immediate means immediate flush, like immediate_buffer_flush = true
+     * -1/full means no explicit flush, let Netty flush when write buffers are full, like immediate_buffer_flush = false
+     * a number of milliseconds to between flushes
+     * */
+    public static final String BUFFER_FLUSH_MS_PROPERTY_NAME = "buffer_flush_millis";
+    public static final String WEB_SOCKET_PORT_PROPERTY_NAME = "websocket_port";
+    public static final String WSS_PORT_PROPERTY_NAME = "secure_websocket_port";
+    public static final String WEB_SOCKET_PATH_PROPERTY_NAME = "websocket_path";
+    public static final String ACL_FILE_PROPERTY_NAME = "acl_file";
+    public static final String PERSISTENT_CLIENT_EXPIRATION_PROPERTY_NAME = "persistent_client_expiration";
+    public static final String SESSION_QUEUE_SIZE = "session_queue_size";
+    public static final String ENABLE_TELEMETRY_NAME = "telemetry_enabled";
+    /**
+     * Defines the SSL implementation to use, default to "JDK".
+     * @see io.netty.handler.ssl.SslProvider#name()
+     */
+    public static final String SSL_PROVIDER = "ssl_provider";
+    public static final String SSL_PORT_PROPERTY_NAME = "ssl_port";
+    public static final String JKS_PATH_PROPERTY_NAME = "jks_path";
+
+    /** @see java.security.KeyStore#getInstance(String) for allowed types, default to "jks" */
+    public static final String KEY_STORE_TYPE = "key_store_type";
+    public static final String KEY_STORE_PASSWORD_PROPERTY_NAME = "key_store_password";
+    public static final String KEY_MANAGER_PASSWORD_PROPERTY_NAME = "key_manager_password";
+    public static final String NETTY_MAX_BYTES_PROPERTY_NAME = "netty.mqtt.message_size";
+    public static final int DEFAULT_NETTY_MAX_BYTES_IN_MESSAGE = 8092;
 
     public abstract void setProperty(String name, String value);
 
diff --git a/distribution/src/main/resources/moquette.conf b/distribution/src/main/resources/moquette.conf
index e464f259..dc873211 100644
--- a/distribution/src/main/resources/moquette.conf
+++ b/distribution/src/main/resources/moquette.conf
@@ -11,7 +11,7 @@ websocket_port 8080
 
 #*********************************************************************
 # Secure Websocket port (wss)
-# decommend this to enable wss
+# decomment this to enable wss
 #*********************************************************************
 # secure_websocket_port 8883
 


Test patch:
diff --git a/broker/src/test/java/io/moquette/broker/config/FluentConfigUsageTest.java b/broker/src/test/java/io/moquette/broker/config/FluentConfigUsageTest.java
new file mode 100644
index 00000000..9dc2e7ba
--- /dev/null
+++ b/broker/src/test/java/io/moquette/broker/config/FluentConfigUsageTest.java
@@ -0,0 +1,34 @@
+package io.moquette.broker.config;
+
+import io.moquette.BrokerConstants;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class FluentConfigUsageTest {
+
+    private void assertPropertyEquals(IConfig config, String expected, String propertyName) {
+        assertEquals(expected, config.getProperty(propertyName));
+    }
+
+    @Test
+    public void checkTLSSubscopeCanConfigureTheRightProperties() {
+        // given
+        IConfig config = new FluentConfig()
+            .withTLS(tls -> {
+                tls.sslProvider(FluentConfig.SSLProvider.SSL);
+                tls.jksPath("/tmp/keystore.jks");
+                tls.keyStoreType(FluentConfig.KeyStoreType.JKS);
+                tls.keyStorePassword("s3cr3t");
+                tls.keyManagerPassword("sup3rs3cr3t");
+            }).build();
+
+        // then after config build
+        // expect the settings are properly set
+        assertPropertyEquals(config, "ssl", BrokerConstants.SSL_PROVIDER);
+        assertPropertyEquals(config, "/tmp/keystore.jks", BrokerConstants.JKS_PATH_PROPERTY_NAME);
+        assertPropertyEquals(config, "jks", BrokerConstants.KEY_STORE_TYPE);
+        assertPropertyEquals(config, "s3cr3t", BrokerConstants.KEY_STORE_PASSWORD_PROPERTY_NAME);
+        assertPropertyEquals(config, "sup3rs3cr3t", BrokerConstants.KEY_MANAGER_PASSWORD_PROPERTY_NAME);
+    }
+}
diff --git a/broker/src/test/java/io/moquette/integration/ServerIntegrationWebSocketTest.java b/broker/src/test/java/io/moquette/integration/ServerIntegrationWebSocketTest.java
index 827a92d2..2911cdda 100644
--- a/broker/src/test/java/io/moquette/integration/ServerIntegrationWebSocketTest.java
+++ b/broker/src/test/java/io/moquette/integration/ServerIntegrationWebSocketTest.java
@@ -18,6 +18,7 @@ package io.moquette.integration;
 
 import io.moquette.broker.Server;
 import io.moquette.BrokerConstants;
+import io.moquette.broker.config.FluentConfig;
 import io.moquette.broker.config.MemoryConfig;
 import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
 import org.eclipse.jetty.websocket.client.WebSocketClient;
@@ -50,13 +51,22 @@ public class ServerIntegrationWebSocketTest {
     Path tempFolder;
 
     protected void startServer(String dbPath) throws IOException {
-        m_server = new Server();
-        final Properties configProps = IntegrationUtils.prepareTestProperties(dbPath);
-        configProps.put(BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME, Integer.toString(BrokerConstants.WEBSOCKET_PORT));
-        configProps.put(BrokerConstants.DATA_PATH_PROPERTY_NAME, dbPath);
-        configProps.put(BrokerConstants.PERSISTENCE_ENABLED_PROPERTY_NAME, "true");
-        m_config = new MemoryConfig(configProps);
-        m_server.startServer(m_config);
+//        m_server = new Server();
+//        m_config = new FluentConfig()
+//            .dataPath(dbPath)
+//            .enablePersistence()
+//            .disableTelemetry()
+//            .websocketPort(BrokerConstants.WEBSOCKET_PORT)
+//            .build();
+//        m_server.startServer(m_config);
+
+        m_server = new Server()
+            .withConfig()
+            .dataPath(dbPath)
+            .enablePersistence()
+            .disableTelemetry()
+            .websocketPort(BrokerConstants.WEBSOCKET_PORT)
+            .startServer();
     }
 
     @BeforeEach