This is an automated email from the ASF dual-hosted git repository.

aleks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new ba765f9b7 FINERACT-1758: Improve custom module example
ba765f9b7 is described below

commit ba765f9b7a398306959aae00571dba6c041ce7c7
Author: Aleks <[email protected]>
AuthorDate: Mon Oct 10 04:54:50 2022 +0200

    FINERACT-1758: Improve custom module example
---
 build.gradle                                       |  80 ++++++-
 .../core => custom/acme/loan/cob}/build.gradle     |   6 +-
 .../acme/loan/cob}/dependencies.gradle             |   2 +-
 .../fineract/loan/cob/AcmeNoopBusinessStep.java    |  56 +++++
 .../acme/loan}/starter/build.gradle                |   6 +-
 .../acme/loan/starter}/dependencies.gradle         |   8 +-
 .../loan/starter/AcmeLoanAutoConfiguration.java    |  13 +-
 .../src/main/resources/META-INF/spring.factories   |   2 +-
 .../loan/starter/AcmeBusinessStepDefinitions.java  |  37 ++--
 .../loan/starter/TestDefaultConfiguration.java     |  51 +++++
 .../com/acme/fineract/loan/starter/TestSuite.java  |  25 +--
 .../src/test/resources/application-test.properties |  13 +-
 .../starter/src/test/resources/cucumber.properties |  14 +-
 .../src/test/resources/features/cob.feature        |  15 +-
 .../loan/starter/src/test/resources/logback.xml    |  41 ++++
 custom/{foo => acme/note}/service/build.gradle     |   6 +-
 .../acme/note/service}/dependencies.gradle         |   3 +-
 .../note/service/AcmeNoteReadPlatformService.java  |  14 +-
 .../note/service/AcmeNoteWritePlatformService.java |  14 +-
 .../acme/note/starter}/build.gradle                |   6 +-
 .../acme/note}/starter/dependencies.gradle         |   9 +-
 .../note/starter/AcmeNoteAutoConfiguration.java}   |  20 +-
 .../src/main/resources/META-INF/spring.factories   |   2 +
 .../db/custom-changelog/changelog-acme-note.xml    |   8 +-
 .../parts/0001_acme_note_initial.xml               |  18 +-
 .../starter/AcmeNoteServiceStepDefinitions.java    |   7 +-
 .../note/starter}/TestDefaultConfiguration.java    |   2 +-
 .../note/starter}/TestOverrideConfiguration.java   |   4 +-
 .../fineract/portfolio/note/starter/TestSuite.java |  15 +-
 .../src/test/resources/application-test.properties |  13 +-
 .../starter/src/test/resources/cucumber.properties |  14 +-
 .../src/test/resources/features/note.feature       |  12 +-
 .../note/starter/src/test/resources/logback.xml    |  41 ++++
 custom/docker/build.gradle                         | 108 +++++++++
 custom/docker/docker-compose.yml                   |  88 ++++++++
 custom/foo/build.gradle                            |  19 --
 custom/foo/service/dependencies.gradle             |  24 --
 .../src/docs/en/chapters/architecture/modules.adoc | 244 ++++-----------------
 .../custom-db-migration-folder-structure.puml      |  25 +++
 .../docs/en/diagrams/custom-folder-structure.puml  |  43 +++-
 .../docs/en/diagrams/modules-folder-structure.puml |  39 ----
 fineract-provider/dependencies.gradle              |   6 +-
 .../service/ShareProductDividendAssembler.java     |   2 +-
 .../resources/db/changelog/db.changelog-master.xml |   1 +
 .../example/DummyServiceStepDefinitions.java       |  54 -----
 .../module/example/TestDefaultConfiguration.java   |  25 ---
 module/build.gradle                                |  19 --
 module/dummy/build.gradle                          |  19 --
 .../fineract/dummy/service/DummyServiceImpl.java   |  30 ---
 .../dummy/starter/DummyAutoConfiguration.java      |  35 ---
 settings.gradle                                    |  20 +-
 51 files changed, 732 insertions(+), 646 deletions(-)

diff --git a/build.gradle b/build.gradle
index af38e01e2..1c7e19d3d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -23,6 +23,7 @@ buildscript {
         retrofitVersion = '2.9.0'
         okhttpVersion = '4.9.3'
         oltuVersion = '1.0.1'
+        fineractCustomProjects = []
         fineractJavaProjects = subprojects.findAll{
             [
                 'fineract-api',
@@ -31,9 +32,6 @@ buildscript {
                 'twofactor-tests',
                 'oauth2-tests',
                 'fineract-client',
-                'core',
-                'service',
-                'starter',
                 'fineract-avro-schemas'
             ].contains(it.name)
         }
@@ -257,6 +255,22 @@ allprojects  {
     }
 }
 
+// NOTE: dynamically load all custom modules
+file("${rootDir}/custom").eachDir { companyDir ->
+    if('build' != companyDir.name && 'docker' != companyDir.name) {
+        file("${rootDir}/custom/${companyDir.name}").eachDir { categoryDir ->
+            if('build' != categoryDir.name) {
+                
file("${rootDir}/custom/${companyDir.name}/${categoryDir.name}").eachDir { 
moduleDir ->
+                    if('build' != moduleDir.name) {
+                        
project.fineractJavaProjects.add(project(":custom:${companyDir.name}:${categoryDir.name}:${moduleDir.name}"))
+                        
project.fineractCustomProjects.add(project(":custom:${companyDir.name}:${categoryDir.name}:${moduleDir.name}"))
+                    }
+                }
+            }
+        }
+    }
+}
+
 configure(project.fineractJavaProjects) {
     // NOTE: order matters!
 
@@ -522,6 +536,9 @@ configure(project.fineractJavaProjects) {
             exclude(module: "slf4j-api")
         }
 
+        compileOnly 'org.projectlombok:lombok'
+        annotationProcessor 'org.projectlombok:lombok'
+
         testImplementation( 'org.mockito:mockito-core',
                 'org.mockito:mockito-junit-jupiter',
                 'org.junit.jupiter:junit-jupiter-api',
@@ -596,6 +613,63 @@ configure(project.fineractJavaProjects) {
     }
 }
 
+configure(project.fineractCustomProjects) {
+    compileJava {
+        dependsOn = []
+    }
+
+    dependencies {
+        implementation (
+                'org.springframework:spring-context',
+                'ch.qos.logback:logback-core',
+                'org.slf4j:slf4j-api',
+                'org.slf4j:log4j-over-slf4j',
+                'org.slf4j:jul-to-slf4j',
+                )
+        implementation ("ch.qos.logback:logback-classic") {
+            exclude(module: "slf4j-api")
+        }
+
+        compileOnly('org.projectlombok:lombok')
+        annotationProcessor('org.projectlombok:lombok')
+        annotationProcessor('org.mapstruct:mapstruct-processor')
+        
annotationProcessor('org.springframework.boot:spring-boot-autoconfigure-processor')
+        
annotationProcessor('org.springframework.boot:spring-boot-configuration-processor')
+
+        testImplementation 
('org.springframework.boot:spring-boot-starter-test') {
+            exclude group: 'com.jayway.jsonpath', module: 'json-path'
+            exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
+            exclude group: 'jakarta.activation'
+            exclude group: 'javax.activation'
+            exclude group: 'org.skyscreamer'
+        }
+        testImplementation( 'org.mockito:mockito-core',
+                'org.mockito:mockito-inline',
+                'org.mockito:mockito-junit-jupiter',
+                'org.junit.jupiter:junit-jupiter-api',
+                'org.junit.jupiter:junit-jupiter-engine',
+                'org.junit.platform:junit-platform-runner', // required to be 
able to run tests directly under Eclipse, see FINERACT-943 & FINERACT-1021
+                'org.bouncycastle:bcpkix-jdk15to18',
+                'org.bouncycastle:bcprov-jdk15to18',
+                'org.awaitility:awaitility',
+                'com.google.truth:truth',
+                'com.google.truth.extensions:truth-java8-extension',
+                'io.github.classgraph:classgraph',
+                'io.cucumber:cucumber-core',
+                'io.cucumber:cucumber-java',
+                'io.cucumber:cucumber-java8',
+                'io.cucumber:cucumber-junit-platform-engine',
+                'io.cucumber:cucumber-spring',
+                )
+
+        testCompileOnly('org.projectlombok:lombok')
+        testAnnotationProcessor('org.projectlombok:lombok')
+        testAnnotationProcessor('org.mapstruct:mapstruct-processor')
+        
testAnnotationProcessor('org.springframework.boot:spring-boot-autoconfigure-processor')
+        
testAnnotationProcessor('org.springframework.boot:spring-boot-configuration-processor')
+    }
+}
+
 configure(project.fineractPublishProjects) {
     apply plugin: 'maven-publish'
     apply plugin: 'signing'
diff --git a/module/dummy/core/build.gradle b/custom/acme/loan/cob/build.gradle
similarity index 87%
rename from module/dummy/core/build.gradle
rename to custom/acme/loan/cob/build.gradle
index bd83b90f7..104d04edc 100644
--- a/module/dummy/core/build.gradle
+++ b/custom/acme/loan/cob/build.gradle
@@ -16,8 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-description = 'Fineract Module: Dummy Core'
+description = 'ACME Corp.: Fineract COB Loan'
 
-group = 'org.apache.fineract.dummy.core'
+group = 'com.acme.fineract.cob'
+
+archivesBaseName = 'acme-fineract-cob-loan'
 
 apply from: 'dependencies.gradle'
diff --git a/module/dummy/core/dependencies.gradle 
b/custom/acme/loan/cob/dependencies.gradle
similarity index 93%
copy from module/dummy/core/dependencies.gradle
copy to custom/acme/loan/cob/dependencies.gradle
index e819cc75b..59d668289 100644
--- a/module/dummy/core/dependencies.gradle
+++ b/custom/acme/loan/cob/dependencies.gradle
@@ -18,5 +18,5 @@
  */
 
 dependencies {
-    implementation('org.springframework:spring-context')
+    implementation(project(':fineract-provider'))
 }
diff --git 
a/custom/acme/loan/cob/src/main/java/com/acme/fineract/loan/cob/AcmeNoopBusinessStep.java
 
b/custom/acme/loan/cob/src/main/java/com/acme/fineract/loan/cob/AcmeNoopBusinessStep.java
new file mode 100644
index 000000000..85d271d51
--- /dev/null
+++ 
b/custom/acme/loan/cob/src/main/java/com/acme/fineract/loan/cob/AcmeNoopBusinessStep.java
@@ -0,0 +1,56 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.acme.fineract.loan.cob;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.cob.loan.LoanCOBBusinessStep;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AcmeNoopBusinessStep implements LoanCOBBusinessStep, 
InitializingBean {
+
+    // NOTE: just to demonstrate that dependency injection is working
+    private final LoanAccountDomainService loanAccountDomainService;
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        log.warn("Acme COB Loan: '{}'", getClass().getCanonicalName());
+    }
+
+    @Override
+    public Loan execute(Loan input) {
+        return input;
+    }
+
+    @Override
+    public String getEnumStyledName() {
+        return "ACME_LOAN_NOOP";
+    }
+
+    @Override
+    public String getHumanReadableName() {
+        return "ACME Loan Noop";
+    }
+}
diff --git a/module/dummy/starter/build.gradle 
b/custom/acme/loan/starter/build.gradle
similarity index 86%
rename from module/dummy/starter/build.gradle
rename to custom/acme/loan/starter/build.gradle
index ab64be956..31e39823f 100644
--- a/module/dummy/starter/build.gradle
+++ b/custom/acme/loan/starter/build.gradle
@@ -16,8 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-description = 'Fineract Module: Dummy Starter'
+description = 'ACME Corp.: Fineract COB Starter'
 
-group = 'org.apache.fineract.dummy.starter'
+group = 'com.acme.fineract.cob'
+
+archivesBaseName = 'acme-fineract-cob-starter'
 
 apply from: 'dependencies.gradle'
diff --git a/module/dummy/service/dependencies.gradle 
b/custom/acme/loan/starter/dependencies.gradle
similarity index 71%
rename from module/dummy/service/dependencies.gradle
rename to custom/acme/loan/starter/dependencies.gradle
index 17ee19c84..6276b6322 100644
--- a/module/dummy/service/dependencies.gradle
+++ b/custom/acme/loan/starter/dependencies.gradle
@@ -18,7 +18,9 @@
  */
 
 dependencies {
-    implementation(project(':module:dummy:core'),
-            'org.springframework:spring-context')
-    testImplementation('io.cucumber:cucumber-spring')
+    implementation(project(':custom:acme:loan:cob'))
+    implementation('org.springframework.boot:spring-boot-starter')
+    testImplementation(project(':fineract-provider'))
+    testImplementation('org.springframework.boot:spring-boot-starter-jdbc')
+    testImplementation('org.springframework.boot:spring-boot-starter-data-jpa')
 }
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java
 
b/custom/acme/loan/starter/src/main/java/com/acme/fineract/loan/starter/AcmeLoanAutoConfiguration.java
similarity index 71%
rename from 
fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java
rename to 
custom/acme/loan/starter/src/main/java/com/acme/fineract/loan/starter/AcmeLoanAutoConfiguration.java
index c22bf6313..91dc820c0 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java
+++ 
b/custom/acme/loan/starter/src/main/java/com/acme/fineract/loan/starter/AcmeLoanAutoConfiguration.java
@@ -16,12 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.module.example;
+package com.acme.fineract.loan.starter;
 
-import org.apache.fineract.infrastructure.core.config.FineractProperties;
-import 
org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.ComponentScan;
 
-@EnableConfigurationProperties({ FineractProperties.class })
-@ComponentScan("com.acmecorp.fineract.foo")
-public class TestFooConfiguration {}
+@AutoConfiguration
+@ComponentScan("com.acme.fineract.loan.cob")
+@ConditionalOnProperty("acme.loan.enabled")
+public class AcmeLoanAutoConfiguration {}
diff --git a/module/dummy/starter/src/main/resources/META-INF/spring.factories 
b/custom/acme/loan/starter/src/main/resources/META-INF/spring.factories
similarity index 53%
rename from module/dummy/starter/src/main/resources/META-INF/spring.factories
rename to custom/acme/loan/starter/src/main/resources/META-INF/spring.factories
index 5dcbd5695..a3c5b7e58 100644
--- a/module/dummy/starter/src/main/resources/META-INF/spring.factories
+++ b/custom/acme/loan/starter/src/main/resources/META-INF/spring.factories
@@ -1,2 +1,2 @@
 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-org.apache.fineract.dummy.starter.DummyAutoConfiguration
\ No newline at end of file
+com.acme.fineract.loan.starter.AcmeLoanAutoConfiguration
\ No newline at end of file
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/module/service/ServiceStepDefinitions.java
 
b/custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/AcmeBusinessStepDefinitions.java
similarity index 54%
copy from 
fineract-provider/src/test/java/org/apache/fineract/module/service/ServiceStepDefinitions.java
copy to 
custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/AcmeBusinessStepDefinitions.java
index 175909734..9e6469bc9 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/module/service/ServiceStepDefinitions.java
+++ 
b/custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/AcmeBusinessStepDefinitions.java
@@ -16,43 +16,54 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.module.service;
+package com.acme.fineract.loan.starter;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
 import io.cucumber.java8.En;
+import java.util.TreeMap;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.cob.COBBusinessStep;
+import org.apache.fineract.cob.COBBusinessStepService;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.springframework.boot.autoconfigure.AutoConfigurations;
 import org.springframework.boot.test.context.runner.ApplicationContextRunner;
 
-public class ServiceStepDefinitions implements En {
+@Slf4j
+public class AcmeBusinessStepDefinitions implements En {
 
-    private Class interfaceClass;
+    private ApplicationContextRunner contextRunner;
 
-    private Class implementationClass;
+    private COBBusinessStepService businessStepService;
 
-    private ApplicationContextRunner contextRunner;
+    private COBBusinessStep<Loan> businessStep;
+
+    private TreeMap<Long, String> result;
 
-    public ServiceStepDefinitions() {
+    public AcmeBusinessStepDefinitions() {
         Given("/^An auto configuration (.*) and a service configuration 
(.*)$/",
                 (String autoConfigurationClassName, String 
configurationClassName) -> {
                     contextRunner = new ApplicationContextRunner()
                             
.withConfiguration(AutoConfigurations.of(Class.forName(autoConfigurationClassName)))
+                            .withPropertyValues("acme.loan.enabled", "true")
                             
.withUserConfiguration(Class.forName(configurationClassName.trim()));
                 });
 
-        When("/^The user retrieves the service of interface class (.*)$/", 
(String interfaceClassName) -> {
+        When("/^The user retrieves the step service with step class (.*) and 
name (.*)$/", (String stepClass, String stepName) -> {
             contextRunner.run((ctx) -> {
-                this.interfaceClass = Class.forName(interfaceClassName.trim());
+                this.businessStepService = 
ctx.getBean(COBBusinessStepService.class);
 
-                assertThat(this.interfaceClass.isInterface()).isTrue();
-                assertThat(ctx).hasSingleBean(this.interfaceClass);
+                this.businessStep = ctx.getBean((Class<COBBusinessStep<Loan>>) 
Class.forName(stepClass));
 
-                this.implementationClass = 
ctx.getBean(interfaceClass).getClass();
+                // TODO: not yet working, because no storage configured/mocked
+                this.result = 
businessStepService.getCOBBusinessStepMap(this.businessStep.getClass(), 
stepName);
             });
         });
 
-        Then("/^The service class should match (.*)$/", (String 
serviceClassName) -> {
-            
assertThat(Class.forName(serviceClassName.trim())).isEqualTo(this.implementationClass);
+        Then("/^The step service should have a result$/", () -> {
+            assertThat(this.businessStep).isNotNull();
+            assertThat(this.result).isNotNull();
+            // log.warn(">>>>>>>>>>>>>>>>>> RESULT: {}", this.result);
         });
     }
 }
diff --git 
a/custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/TestDefaultConfiguration.java
 
b/custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/TestDefaultConfiguration.java
new file mode 100644
index 000000000..39e463ff3
--- /dev/null
+++ 
b/custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/TestDefaultConfiguration.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.acme.fineract.loan.starter;
+
+import static org.mockito.Mockito.mock;
+
+import org.apache.fineract.cob.COBBusinessStepService;
+import org.apache.fineract.cob.COBBusinessStepServiceImpl;
+import org.apache.fineract.cob.domain.BatchBusinessStepRepository;
+import org.apache.fineract.infrastructure.core.config.FineractProperties;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
+import org.springframework.beans.factory.ListableBeanFactory;
+import 
org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+
+@EnableConfigurationProperties({ FineractProperties.class })
+public class TestDefaultConfiguration {
+
+    @Bean
+    public BatchBusinessStepRepository batchBusinessStepRepository() {
+        return mock(BatchBusinessStepRepository.class);
+    }
+
+    @Bean
+    public COBBusinessStepService 
cobBusinessStepService(BatchBusinessStepRepository batchBusinessStepRepository,
+            ApplicationContext context, ListableBeanFactory beanFactory) {
+        return new COBBusinessStepServiceImpl(batchBusinessStepRepository, 
context, beanFactory);
+    }
+
+    @Bean
+    public LoanAccountDomainService loanAccountDomainService() {
+        return mock(LoanAccountDomainService.class);
+    }
+}
diff --git 
a/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/data/DummyMessage.java
 
b/custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/TestSuite.java
similarity index 61%
rename from 
module/dummy/core/src/main/java/org/apache/fineract/dummy/core/data/DummyMessage.java
rename to 
custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/TestSuite.java
index c6e35f712..cd1c32d3c 100644
--- 
a/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/data/DummyMessage.java
+++ 
b/custom/acme/loan/starter/src/test/java/com/acme/fineract/loan/starter/TestSuite.java
@@ -16,21 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.dummy.core.data;
+package com.acme.fineract.loan.starter;
 
-public class DummyMessage {
+import io.cucumber.spring.CucumberContextConfiguration;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.context.web.WebAppConfiguration;
 
-    private String message;
+@CucumberContextConfiguration
+@ExtendWith(SpringExtension.class)
+@TestPropertySource("classpath:application-test.properties")
+@WebAppConfiguration
+public class TestSuite {
 
-    public DummyMessage(String message) {
-        this.message = message;
-    }
-
-    public String getMessage() {
-        return message;
-    }
-
-    public void setMessage(String message) {
-        this.message = message;
-    }
 }
diff --git 
a/fineract-provider/src/test/resources/features/module/modules.example.feature 
b/custom/acme/loan/starter/src/test/resources/application-test.properties
similarity index 57%
copy from 
fineract-provider/src/test/resources/features/module/modules.example.feature
copy to custom/acme/loan/starter/src/test/resources/application-test.properties
index c4454a4f1..0a2848936 100644
--- 
a/fineract-provider/src/test/resources/features/module/modules.example.feature
+++ b/custom/acme/loan/starter/src/test/resources/application-test.properties
@@ -17,15 +17,4 @@
 # under the License.
 #
 
-Feature: Example Modules
-
-  @modules
-  Scenario Outline: Verify that the dummy service returns the correct message
-    Given A dummy service configuration <configurationClass>
-    When The user gets the dummy service message
-    Then The dummy service message should match <message>
-
-    Examples:
-      | configurationClass                                          | message  
             |
-      | org.apache.fineract.module.example.TestDefaultConfiguration | Hello: 
DEFAULT DUMMY! |
-      | org.apache.fineract.module.example.TestFooConfiguration     | Hello: 
FOO!           |
\ No newline at end of file
+# test properties
diff --git 
a/fineract-provider/src/test/resources/features/module/modules.example.feature 
b/custom/acme/loan/starter/src/test/resources/cucumber.properties
similarity index 57%
copy from 
fineract-provider/src/test/resources/features/module/modules.example.feature
copy to custom/acme/loan/starter/src/test/resources/cucumber.properties
index c4454a4f1..b3ee387c2 100644
--- 
a/fineract-provider/src/test/resources/features/module/modules.example.feature
+++ b/custom/acme/loan/starter/src/test/resources/cucumber.properties
@@ -17,15 +17,5 @@
 # under the License.
 #
 
-Feature: Example Modules
-
-  @modules
-  Scenario Outline: Verify that the dummy service returns the correct message
-    Given A dummy service configuration <configurationClass>
-    When The user gets the dummy service message
-    Then The dummy service message should match <message>
-
-    Examples:
-      | configurationClass                                          | message  
             |
-      | org.apache.fineract.module.example.TestDefaultConfiguration | Hello: 
DEFAULT DUMMY! |
-      | org.apache.fineract.module.example.TestFooConfiguration     | Hello: 
FOO!           |
\ No newline at end of file
+cucumber.publish.quiet=true
+cucumber.glue=com.acme.fineract
diff --git 
a/fineract-provider/src/test/resources/features/module/modules.example.feature 
b/custom/acme/loan/starter/src/test/resources/features/cob.feature
similarity index 52%
copy from 
fineract-provider/src/test/resources/features/module/modules.example.feature
copy to custom/acme/loan/starter/src/test/resources/features/cob.feature
index c4454a4f1..871728f85 100644
--- 
a/fineract-provider/src/test/resources/features/module/modules.example.feature
+++ b/custom/acme/loan/starter/src/test/resources/features/cob.feature
@@ -17,15 +17,14 @@
 # under the License.
 #
 
-Feature: Example Modules
+Feature: COB Feature
 
   @modules
-  Scenario Outline: Verify that the dummy service returns the correct message
-    Given A dummy service configuration <configurationClass>
-    When The user gets the dummy service message
-    Then The dummy service message should match <message>
+  Scenario Outline: Verify that the dependency injection of steps into the 
step service works
+    Given An auto configuration <autoConfigurationClass> and a service 
configuration <configurationClass>
+    When The user retrieves the step service with step class <stepClass> and 
name <stepName>
+    Then The step service should have a result
 
     Examples:
-      | configurationClass                                          | message  
             |
-      | org.apache.fineract.module.example.TestDefaultConfiguration | Hello: 
DEFAULT DUMMY! |
-      | org.apache.fineract.module.example.TestFooConfiguration     | Hello: 
FOO!           |
\ No newline at end of file
+      | autoConfigurationClass                                  | 
configurationClass                                      | stepClass             
                          | stepName       |
+      | com.acme.fineract.loan.starter.AcmeLoanAutoConfiguration | 
com.acme.fineract.loan.starter.TestDefaultConfiguration | 
com.acme.fineract.loan.cob.AcmeNoopBusinessStep | ACME_LOAN_NOOP |
diff --git a/custom/acme/loan/starter/src/test/resources/logback.xml 
b/custom/acme/loan/starter/src/test/resources/logback.xml
new file mode 100644
index 000000000..30cf96640
--- /dev/null
+++ b/custom/acme/loan/starter/src/test/resources/logback.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<configuration scan="false">
+    <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
+        <resetJUL>false</resetJUL>
+    </contextListener>
+
+    <appender name="STDOUT" target="System.out" 
class="ch.qos.logback.core.ConsoleAppender">
+        <withJansi>true</withJansi>
+        <encoder>
+            <pattern>%green(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%thread] 
%highlight(%-5level) %cyan(%logger{36}) - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.springframework.transaction" level="INFO" />
+    <logger name="org.springframework.data.convert" level="ERROR" />
+    <logger name="org.springframework.http.converter.json" level="ERROR" />
+
+    <root level="INFO">
+        <appender-ref ref="STDOUT" />
+    </root>
+</configuration>
diff --git a/custom/foo/service/build.gradle 
b/custom/acme/note/service/build.gradle
similarity index 85%
rename from custom/foo/service/build.gradle
rename to custom/acme/note/service/build.gradle
index 8a0f207ee..90fdb314f 100644
--- a/custom/foo/service/build.gradle
+++ b/custom/acme/note/service/build.gradle
@@ -16,8 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-description = 'ACME Corp.: Fineract Foo Service'
+description = 'ACME Corp.: Fineract Note Service'
 
-group = 'com.acmecorp.fineract.foo.service'
+group = 'com.acme.fineract.portfolio.note'
+
+archivesBaseName = 'acme-fineract-note-service'
 
 apply from: 'dependencies.gradle'
diff --git a/module/dummy/core/dependencies.gradle 
b/custom/acme/note/service/dependencies.gradle
similarity index 87%
rename from module/dummy/core/dependencies.gradle
rename to custom/acme/note/service/dependencies.gradle
index e819cc75b..a81ef21a1 100644
--- a/module/dummy/core/dependencies.gradle
+++ b/custom/acme/note/service/dependencies.gradle
@@ -18,5 +18,6 @@
  */
 
 dependencies {
-    implementation('org.springframework:spring-context')
+    implementation(project(':fineract-provider'))
+    compileOnly('org.springframework.boot:spring-boot-autoconfigure')
 }
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteReadPlatformService.java
 
b/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteReadPlatformService.java
similarity index 71%
rename from 
fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteReadPlatformService.java
rename to 
custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteReadPlatformService.java
index 7d4e5c0b0..3898ba030 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteReadPlatformService.java
+++ 
b/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteReadPlatformService.java
@@ -16,15 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.module.service.custom;
+package com.acme.fineract.portfolio.note.service;
 
 import java.util.Collection;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.portfolio.note.data.NoteData;
 import org.apache.fineract.portfolio.note.service.NoteReadPlatformService;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Service;
 
+@Slf4j
 @Service
-public class CustomNoteReadPlatformService implements NoteReadPlatformService {
+@ConditionalOnProperty("acme.note.enabled")
+public class AcmeNoteReadPlatformService implements NoteReadPlatformService, 
InitializingBean {
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        log.warn("Note Read Service: '{}'", getClass().getCanonicalName());
+    }
 
     @Override
     public NoteData retrieveNote(Long noteId, Long resourceId, Integer 
noteTypeId) {
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteWritePlatformService.java
 
b/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteWritePlatformService.java
similarity index 75%
rename from 
fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteWritePlatformService.java
rename to 
custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteWritePlatformService.java
index ecc82db90..1fb0572a8 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/module/service/custom/CustomNoteWritePlatformService.java
+++ 
b/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteWritePlatformService.java
@@ -16,16 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.module.service.custom;
+package com.acme.fineract.portfolio.note.service;
 
+import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import org.apache.fineract.portfolio.client.domain.Client;
 import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Service;
 
+@Slf4j
 @Service
-public class CustomNoteWritePlatformService implements 
NoteWritePlatformService {
+@ConditionalOnProperty("acme.note.enabled")
+public class AcmeNoteWritePlatformService implements NoteWritePlatformService, 
InitializingBean {
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        log.warn("Note Write Service: '{}'", getClass().getCanonicalName());
+    }
 
     @Override
     public CommandProcessingResult createNote(JsonCommand command) {
diff --git a/module/dummy/service/build.gradle 
b/custom/acme/note/starter/build.gradle
similarity index 85%
rename from module/dummy/service/build.gradle
rename to custom/acme/note/starter/build.gradle
index 8e59db880..49bf235b5 100644
--- a/module/dummy/service/build.gradle
+++ b/custom/acme/note/starter/build.gradle
@@ -16,8 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-description = 'Fineract Module: Dummy Service'
+description = 'ACME Corp.: Fineract Note Starter'
 
-group = 'org.apache.fineract.dummy.service'
+group = 'com.acme.fineract.portfolio.note'
+
+archivesBaseName = 'acme-fineract-note-starter'
 
 apply from: 'dependencies.gradle'
diff --git a/module/dummy/starter/dependencies.gradle 
b/custom/acme/note/starter/dependencies.gradle
similarity index 71%
rename from module/dummy/starter/dependencies.gradle
rename to custom/acme/note/starter/dependencies.gradle
index cb70e5d10..3fd62233d 100644
--- a/module/dummy/starter/dependencies.gradle
+++ b/custom/acme/note/starter/dependencies.gradle
@@ -18,8 +18,9 @@
  */
 
 dependencies {
-    implementation(project(':module:dummy:core'),
-            project(':module:dummy:service'),
-            'org.springframework.boot:spring-boot-starter')
-    testImplementation('io.cucumber:cucumber-spring')
+    implementation(project(':custom:acme:note:service'))
+    implementation('org.springframework.boot:spring-boot-starter')
+    testImplementation(project(':fineract-provider'))
+    testImplementation('org.springframework.boot:spring-boot-starter-jdbc')
+    testImplementation('org.springframework.boot:spring-boot-starter-data-jpa')
 }
diff --git 
a/custom/foo/service/src/main/java/com/acmecorp/fineract/foo/service/FooDummyServiceImpl.java
 
b/custom/acme/note/starter/src/main/java/com/acme/fineract/portfolio/note/starter/AcmeNoteAutoConfiguration.java
similarity index 66%
rename from 
custom/foo/service/src/main/java/com/acmecorp/fineract/foo/service/FooDummyServiceImpl.java
rename to 
custom/acme/note/starter/src/main/java/com/acme/fineract/portfolio/note/starter/AcmeNoteAutoConfiguration.java
index 059224b22..60629ef91 100644
--- 
a/custom/foo/service/src/main/java/com/acmecorp/fineract/foo/service/FooDummyServiceImpl.java
+++ 
b/custom/acme/note/starter/src/main/java/com/acme/fineract/portfolio/note/starter/AcmeNoteAutoConfiguration.java
@@ -16,17 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package com.acmecorp.fineract.foo.service;
+package com.acme.fineract.portfolio.note.starter;
 
-import org.apache.fineract.dummy.core.data.DummyMessage;
-import org.apache.fineract.dummy.core.service.DummyService;
-import org.springframework.stereotype.Service;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.ComponentScan;
 
-@Service
-public class FooDummyServiceImpl implements DummyService {
-
-    @Override
-    public DummyMessage getMessage() {
-        return new DummyMessage("Hello: FOO!");
-    }
-}
+@AutoConfiguration
+@ComponentScan("com.acme.fineract.portfolio.note")
+@ConditionalOnProperty("acme.note.enabled")
+public class AcmeNoteAutoConfiguration {}
diff --git 
a/custom/acme/note/starter/src/main/resources/META-INF/spring.factories 
b/custom/acme/note/starter/src/main/resources/META-INF/spring.factories
new file mode 100644
index 000000000..d3f0e811b
--- /dev/null
+++ b/custom/acme/note/starter/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+com.acme.fineract.portfolio.note.starter.AcmeNoteAutoConfiguration
\ No newline at end of file
diff --git 
a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml 
b/custom/acme/note/starter/src/main/resources/db/custom-changelog/changelog-acme-note.xml
similarity index 60%
copy from 
fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
copy to 
custom/acme/note/starter/src/main/resources/db/custom-changelog/changelog-acme-note.xml
index 5bc9e3cd3..601ba8f08 100644
--- a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
+++ 
b/custom/acme/note/starter/src/main/resources/db/custom-changelog/changelog-acme-note.xml
@@ -22,11 +22,5 @@
 <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
                    
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd";>
-    <property name="current_date" value="CURDATE()" context="mysql"/>
-    <property name="current_date" value="CURRENT_DATE" context="postgresql"/>
-    <property name="current_datetime" value="NOW()"/>
-    <include file="tenant-store/initial-switch-changelog-tenant-store.xml" 
relativeToChangelogFile="true" context="tenant_store_db AND initial_switch"/>
-    <include file="tenant-store/changelog-tenant-store.xml" 
relativeToChangelogFile="true" context="tenant_store_db AND !initial_switch"/>
-    <include file="tenant/initial-switch-changelog-tenant.xml" 
relativeToChangelogFile="true" context="tenant_db AND initial_switch"/>
-    <include file="tenant/changelog-tenant.xml" relativeToChangelogFile="true" 
context="tenant_db AND !initial_switch"/>
+    <include file="parts/0001_acme_note_initial.xml" 
relativeToChangelogFile="true"/>
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml 
b/custom/acme/note/starter/src/main/resources/db/custom-changelog/parts/0001_acme_note_initial.xml
similarity index 60%
copy from 
fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
copy to 
custom/acme/note/starter/src/main/resources/db/custom-changelog/parts/0001_acme_note_initial.xml
index 5bc9e3cd3..e7fa227b3 100644
--- a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
+++ 
b/custom/acme/note/starter/src/main/resources/db/custom-changelog/parts/0001_acme_note_initial.xml
@@ -22,11 +22,15 @@
 <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
                    
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd";>
-    <property name="current_date" value="CURDATE()" context="mysql"/>
-    <property name="current_date" value="CURRENT_DATE" context="postgresql"/>
-    <property name="current_datetime" value="NOW()"/>
-    <include file="tenant-store/initial-switch-changelog-tenant-store.xml" 
relativeToChangelogFile="true" context="tenant_store_db AND initial_switch"/>
-    <include file="tenant-store/changelog-tenant-store.xml" 
relativeToChangelogFile="true" context="tenant_store_db AND !initial_switch"/>
-    <include file="tenant/initial-switch-changelog-tenant.xml" 
relativeToChangelogFile="true" context="tenant_db AND initial_switch"/>
-    <include file="tenant/changelog-tenant.xml" relativeToChangelogFile="true" 
context="tenant_db AND !initial_switch"/>
+    <changeSet author="acme" id="1">
+        <createTable tableName="acme_note_dummy">
+            <column autoIncrement="true" name="id" type="BIGINT">
+                <constraints nullable="false" primaryKey="true"/>
+            </column>
+            <column name="name" type="VARCHAR(100)">
+                <constraints unique="true"/>
+            </column>
+            <column name="description" type="VARCHAR(500)"/>
+        </createTable>
+    </changeSet>
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/module/service/ServiceStepDefinitions.java
 
b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/AcmeNoteServiceStepDefinitions.java
similarity index 90%
rename from 
fineract-provider/src/test/java/org/apache/fineract/module/service/ServiceStepDefinitions.java
rename to 
custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/AcmeNoteServiceStepDefinitions.java
index 175909734..5807e0b80 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/module/service/ServiceStepDefinitions.java
+++ 
b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/AcmeNoteServiceStepDefinitions.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.module.service;
+package com.acme.fineract.portfolio.note.starter;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -24,7 +24,7 @@ import io.cucumber.java8.En;
 import org.springframework.boot.autoconfigure.AutoConfigurations;
 import org.springframework.boot.test.context.runner.ApplicationContextRunner;
 
-public class ServiceStepDefinitions implements En {
+public class AcmeNoteServiceStepDefinitions implements En {
 
     private Class interfaceClass;
 
@@ -32,11 +32,12 @@ public class ServiceStepDefinitions implements En {
 
     private ApplicationContextRunner contextRunner;
 
-    public ServiceStepDefinitions() {
+    public AcmeNoteServiceStepDefinitions() {
         Given("/^An auto configuration (.*) and a service configuration 
(.*)$/",
                 (String autoConfigurationClassName, String 
configurationClassName) -> {
                     contextRunner = new ApplicationContextRunner()
                             
.withConfiguration(AutoConfigurations.of(Class.forName(autoConfigurationClassName)))
+                            .withPropertyValues("acme.note.enabled", "true")
                             
.withUserConfiguration(Class.forName(configurationClassName.trim()));
                 });
 
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/module/service/TestDefaultConfiguration.java
 
b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestDefaultConfiguration.java
similarity index 98%
rename from 
fineract-provider/src/test/java/org/apache/fineract/module/service/TestDefaultConfiguration.java
rename to 
custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestDefaultConfiguration.java
index ffe76f46b..da74531f2 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/module/service/TestDefaultConfiguration.java
+++ 
b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestDefaultConfiguration.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.module.service;
+package com.acme.fineract.portfolio.note.starter;
 
 import static org.mockito.Mockito.mock;
 
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/module/service/TestOverrideConfiguration.java
 
b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestOverrideConfiguration.java
similarity index 96%
rename from 
fineract-provider/src/test/java/org/apache/fineract/module/service/TestOverrideConfiguration.java
rename to 
custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestOverrideConfiguration.java
index 6abb1ad04..173671a0b 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/module/service/TestOverrideConfiguration.java
+++ 
b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestOverrideConfiguration.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.module.service;
+package com.acme.fineract.portfolio.note.starter;
 
 import static org.mockito.Mockito.mock;
 
@@ -33,7 +33,7 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.jdbc.core.JdbcTemplate;
 
-@ComponentScan("org.apache.fineract.module.service.custom")
+@ComponentScan("com.acme.fineract")
 public class TestOverrideConfiguration {
     // NOTE: unfortunately an abastract base class that contains all these 
mock functions won't work
 
diff --git 
a/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/service/DummyService.java
 
b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestSuite.java
similarity index 60%
rename from 
module/dummy/core/src/main/java/org/apache/fineract/dummy/core/service/DummyService.java
rename to 
custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestSuite.java
index fdcd4a17e..71bd92b50 100644
--- 
a/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/service/DummyService.java
+++ 
b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestSuite.java
@@ -16,11 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.dummy.core.service;
+package com.acme.fineract.portfolio.note.starter;
 
-import org.apache.fineract.dummy.core.data.DummyMessage;
+import io.cucumber.spring.CucumberContextConfiguration;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.context.web.WebAppConfiguration;
 
-public interface DummyService {
+@CucumberContextConfiguration
+@ExtendWith(SpringExtension.class)
+@TestPropertySource("classpath:application-test.properties")
+@WebAppConfiguration
+public class TestSuite {
 
-    DummyMessage getMessage();
 }
diff --git 
a/fineract-provider/src/test/resources/features/module/modules.example.feature 
b/custom/acme/note/starter/src/test/resources/application-test.properties
similarity index 57%
copy from 
fineract-provider/src/test/resources/features/module/modules.example.feature
copy to custom/acme/note/starter/src/test/resources/application-test.properties
index c4454a4f1..0a2848936 100644
--- 
a/fineract-provider/src/test/resources/features/module/modules.example.feature
+++ b/custom/acme/note/starter/src/test/resources/application-test.properties
@@ -17,15 +17,4 @@
 # under the License.
 #
 
-Feature: Example Modules
-
-  @modules
-  Scenario Outline: Verify that the dummy service returns the correct message
-    Given A dummy service configuration <configurationClass>
-    When The user gets the dummy service message
-    Then The dummy service message should match <message>
-
-    Examples:
-      | configurationClass                                          | message  
             |
-      | org.apache.fineract.module.example.TestDefaultConfiguration | Hello: 
DEFAULT DUMMY! |
-      | org.apache.fineract.module.example.TestFooConfiguration     | Hello: 
FOO!           |
\ No newline at end of file
+# test properties
diff --git 
a/fineract-provider/src/test/resources/features/module/modules.example.feature 
b/custom/acme/note/starter/src/test/resources/cucumber.properties
similarity index 57%
rename from 
fineract-provider/src/test/resources/features/module/modules.example.feature
rename to custom/acme/note/starter/src/test/resources/cucumber.properties
index c4454a4f1..b3ee387c2 100644
--- 
a/fineract-provider/src/test/resources/features/module/modules.example.feature
+++ b/custom/acme/note/starter/src/test/resources/cucumber.properties
@@ -17,15 +17,5 @@
 # under the License.
 #
 
-Feature: Example Modules
-
-  @modules
-  Scenario Outline: Verify that the dummy service returns the correct message
-    Given A dummy service configuration <configurationClass>
-    When The user gets the dummy service message
-    Then The dummy service message should match <message>
-
-    Examples:
-      | configurationClass                                          | message  
             |
-      | org.apache.fineract.module.example.TestDefaultConfiguration | Hello: 
DEFAULT DUMMY! |
-      | org.apache.fineract.module.example.TestFooConfiguration     | Hello: 
FOO!           |
\ No newline at end of file
+cucumber.publish.quiet=true
+cucumber.glue=com.acme.fineract
diff --git 
a/fineract-provider/src/test/resources/features/module/modules.service.feature 
b/custom/acme/note/starter/src/test/resources/features/note.feature
similarity index 56%
rename from 
fineract-provider/src/test/resources/features/module/modules.service.feature
rename to custom/acme/note/starter/src/test/resources/features/note.feature
index e2cc191a4..5895c75c8 100644
--- 
a/fineract-provider/src/test/resources/features/module/modules.service.feature
+++ b/custom/acme/note/starter/src/test/resources/features/note.feature
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-Feature: Service Modules
+Feature: Note Feature
 
   @modules
   Scenario Outline: Verify that the dummy service returns the correct message
@@ -26,8 +26,8 @@ Feature: Service Modules
     Then The service class should match <serviceClass>
 
     Examples:
-      | autoConfigurationClass                                           | 
configurationClass                                           | interfaceClass   
                                                   | serviceClass               
                                                          |
-      | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | 
org.apache.fineract.module.service.TestDefaultConfiguration  | 
org.apache.fineract.portfolio.note.service.NoteReadPlatformService  | 
org.apache.fineract.portfolio.note.service.NoteReadPlatformServiceImpl          
     |
-      | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | 
org.apache.fineract.module.service.TestDefaultConfiguration  | 
org.apache.fineract.portfolio.note.service.NoteWritePlatformService | 
org.apache.fineract.portfolio.note.service.NoteWritePlatformServiceJpaRepositoryImpl
 |
-      | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | 
org.apache.fineract.module.service.TestOverrideConfiguration | 
org.apache.fineract.portfolio.note.service.NoteReadPlatformService  | 
org.apache.fineract.module.service.custom.CustomNoteReadPlatformService         
     |
-      | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | 
org.apache.fineract.module.service.TestOverrideConfiguration | 
org.apache.fineract.portfolio.note.service.NoteWritePlatformService | 
org.apache.fineract.module.service.custom.CustomNoteWritePlatformService        
     |
\ No newline at end of file
+      | autoConfigurationClass                                           | 
configurationClass                                                 | 
interfaceClass                                                      | 
serviceClass                                                                    
     |
+      | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | 
com.acme.fineract.portfolio.note.starter.TestDefaultConfiguration  | 
org.apache.fineract.portfolio.note.service.NoteReadPlatformService  | 
org.apache.fineract.portfolio.note.service.NoteReadPlatformServiceImpl          
     |
+      | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | 
com.acme.fineract.portfolio.note.starter.TestDefaultConfiguration  | 
org.apache.fineract.portfolio.note.service.NoteWritePlatformService | 
org.apache.fineract.portfolio.note.service.NoteWritePlatformServiceJpaRepositoryImpl
 |
+      | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | 
com.acme.fineract.portfolio.note.starter.TestOverrideConfiguration | 
org.apache.fineract.portfolio.note.service.NoteReadPlatformService  | 
com.acme.fineract.portfolio.note.service.AcmeNoteReadPlatformService            
     |
+      | org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration | 
com.acme.fineract.portfolio.note.starter.TestOverrideConfiguration | 
org.apache.fineract.portfolio.note.service.NoteWritePlatformService | 
com.acme.fineract.portfolio.note.service.AcmeNoteWritePlatformService           
     |
\ No newline at end of file
diff --git a/custom/acme/note/starter/src/test/resources/logback.xml 
b/custom/acme/note/starter/src/test/resources/logback.xml
new file mode 100644
index 000000000..30cf96640
--- /dev/null
+++ b/custom/acme/note/starter/src/test/resources/logback.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<configuration scan="false">
+    <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
+        <resetJUL>false</resetJUL>
+    </contextListener>
+
+    <appender name="STDOUT" target="System.out" 
class="ch.qos.logback.core.ConsoleAppender">
+        <withJansi>true</withJansi>
+        <encoder>
+            <pattern>%green(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%thread] 
%highlight(%-5level) %cyan(%logger{36}) - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.springframework.transaction" level="INFO" />
+    <logger name="org.springframework.data.convert" level="ERROR" />
+    <logger name="org.springframework.http.converter.json" level="ERROR" />
+
+    <root level="INFO">
+        <appender-ref ref="STDOUT" />
+    </root>
+</configuration>
diff --git a/custom/docker/build.gradle b/custom/docker/build.gradle
new file mode 100644
index 000000000..8d1a91711
--- /dev/null
+++ b/custom/docker/build.gradle
@@ -0,0 +1,108 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+description = 'Fineract Custom Docker Image'
+
+apply plugin: 'java'
+apply plugin: 'com.google.cloud.tools.jib'
+apply from: 
"${rootDir}/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle"
+
+jib {
+    from {
+        image = 'azul/zulu-openjdk-alpine:17'
+    }
+
+    to {
+        image = 'fineract-custom'
+        tags = [
+            "${project.version}",
+            'latest'
+        ]
+    }
+
+    container {
+        creationTime = 'USE_CURRENT_TIMESTAMP'
+        mainClass = 'org.apache.fineract.ServerApplication'
+        jvmFlags = [
+            '-Xms1G',
+            '-XshowSettings:vm',
+            '-XX:+UseContainerSupport',
+            '-XX:+UseStringDeduplication',
+            '-XX:MinRAMPercentage=25',
+            '-XX:MaxRAMPercentage=80',
+            '--add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED',
+            '--add-opens=java.base/java.lang=ALL-UNNAMED',
+            '--add-opens=java.base/java.lang.invoke=ALL-UNNAMED',
+            '--add-opens=java.base/java.io=ALL-UNNAMED',
+            '--add-opens=java.base/java.security=ALL-UNNAMED',
+            '--add-opens=java.base/java.util=ALL-UNNAMED',
+            '--add-opens=java.management/javax.management=ALL-UNNAMED',
+            '--add-opens=java.naming/javax.naming=ALL-UNNAMED'
+        ]
+        args = [
+            '-Duser.home=/tmp',
+            '-Dfile.encoding=UTF-8',
+            '-Duser.timezone=UTC',
+            '-Djava.security.egd=file:/dev/./urandom'
+        ]
+        ports = ['8080/tcp', '8443/tcp']
+        labels = [maintainer: 'Aleksandar Vidakovic <[email protected]>']
+        user = 'nobody:nogroup'
+    }
+
+    allowInsecureRegistries = true
+
+    dependencies {
+        implementation project(':fineract-provider')
+        // NOTE: dynamically load all custom modules
+        file("${rootDir}/custom").eachDir { companyDir ->
+            if('build' != companyDir.name && 'docker' != companyDir.name) {
+                file("${rootDir}/custom/${companyDir.name}").eachDir { 
categoryDir ->
+                    if('build' != categoryDir.name) {
+                        
file("${rootDir}/custom/${companyDir.name}/${categoryDir.name}").eachDir { 
moduleDir ->
+                            if('build' != moduleDir.name) {
+                                implementation 
project(":custom:${companyDir.name}:${categoryDir.name}:${moduleDir.name}")
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        // NOTE: other custom dependencies
+        implementation 'org.mariadb.jdbc:mariadb-java-client:3.0.7'
+        implementation 'mysql:mysql-connector-java:8.0.30'
+        implementation 'org.postgresql:postgresql:42.5.0'
+        
annotationProcessor('org.springframework.boot:spring-boot-autoconfigure-processor')
+        
annotationProcessor('org.springframework.boot:spring-boot-configuration-processor')
+    }
+
+    pluginExtensions {
+        pluginExtension {
+            implementation = 
'com.google.cloud.tools.jib.gradle.extension.layerfilter.JibLayerFilterExtension'
+            configuration {
+                filters {
+                    filter {
+                        glob = '/app/resources/**'
+                    }
+                }
+            }
+        }
+    }
+}
+
+// tasks.jibDockerBuild.dependsOn(':fineract-provider:bootJar')
diff --git a/custom/docker/docker-compose.yml b/custom/docker/docker-compose.yml
new file mode 100644
index 000000000..29814498e
--- /dev/null
+++ b/custom/docker/docker-compose.yml
@@ -0,0 +1,88 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# NB: Travis CI (dist: bionic) supports up to v3.7
+# (unless we'd install a more recent version in .travis.yml)
+version: '3.7'
+services:
+  # Backend service
+  fineractmysql:
+    image: mariadb:10.9
+    volumes:
+      - 
../../fineract-db/server_collation.cnf:/etc/mysql/conf.d/server_collation.cnf
+      - ../../fineract-db/docker:/docker-entrypoint-initdb.d:Z,ro
+    restart: always
+    environment:
+      MYSQL_ROOT_PASSWORD: skdcnwauicn2ucnaecasdsajdnizucawencascdca
+    healthcheck:
+      test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost", 
"--password=skdcnwauicn2ucnaecasdsajdnizucawencascdca" ]
+      timeout: 10s
+      retries: 10
+    ports:
+      - "3306:3306"
+  fineract-server:
+    image: fineract-custom:latest
+    volumes:
+      - ../../fineract-provider/build/data:/data
+    healthcheck:
+      test: ["CMD", 'sh', '-c', 'echo -e "Checking for the availability of 
Fineract server deployment"; while ! nc -z "fineract-server" 8443; do sleep 1; 
printf "-"; done; echo -e " >> Fineract server has started";' ]
+      timeout: 10s
+      retries: 10
+    ports:
+      - 8443:8443
+    depends_on:
+      fineractmysql:
+        condition: service_healthy
+    environment:
+      # NOTE: node aware scheduler
+      - FINERACT_NODE_ID=1
+      # NOTE: env vars prefixed "FINERACT_HIKARI_*" are used to configure the 
database connection pool
+      - FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME=org.mariadb.jdbc.Driver
+      - 
FINERACT_HIKARI_JDBC_URL=jdbc:mariadb://fineractmysql:3306/fineract_tenants
+      - FINERACT_HIKARI_USERNAME=root
+      - FINERACT_HIKARI_PASSWORD=skdcnwauicn2ucnaecasdsajdnizucawencascdca
+      # ... following variables are optional; "application.properties" 
contains reasonable defaults (same as here)
+      - FINERACT_HIKARI_MINIMUM_IDLE=3
+      - FINERACT_HIKARI_MAXIMUM_POOL_SIZE=10
+      - FINERACT_HIKARI_IDLE_TIMEOUT=60000
+      - FINERACT_HIKARI_CONNECTION_TIMEOUT=20000
+      - FINERACT_HIKARI_TEST_QUERY=SELECT 1
+      - FINERACT_HIKARI_AUTO_COMMIT=true
+      - FINERACT_HIKARI_DS_PROPERTIES_CACHE_PREP_STMTS=true
+      - FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SIZE=250
+      - FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SQL_LIMIT=2048
+      - FINERACT_HIKARI_DS_PROPERTIES_USE_SERVER_PREP_STMTS=true
+      - FINERACT_HIKARI_DS_PROPERTIES_USE_LOCAL_SESSION_STATE=true
+      - FINERACT_HIKARI_DS_PROPERTIES_REWRITE_BATCHED_STATEMENTS=true
+      - FINERACT_HIKARI_DS_PROPERTIES_CACHE_RESULT_SET_METADATA=true
+      - FINERACT_HIKARI_DS_PROPERTIES_CACHE_SERVER_CONFIGURATION=true
+      - FINERACT_HIKARI_DS_PROPERTIES_ELIDE_SET_AUTO_COMMITS=true
+      - FINERACT_HIKARI_DS_PROPERTIES_MAINTAIN_TIME_STATS=false
+      - FINERACT_HIKARI_DS_PROPERTIES_LOG_SLOW_QUERIES=true
+      - FINERACT_HIKARI_DS_PROPERTIES_DUMP_QUERIES_IN_EXCEPTION=true
+      # NOTE: env vars prefixed "FINERACT_DEFAULT_TENANTDB_*" are used to 
create the default tenant database
+      - FINERACT_DEFAULT_TENANTDB_HOSTNAME=fineractmysql
+      - FINERACT_DEFAULT_TENANTDB_PORT=3306
+      - FINERACT_DEFAULT_TENANTDB_UID=root
+      - FINERACT_DEFAULT_TENANTDB_PWD=skdcnwauicn2ucnaecasdsajdnizucawencascdca
+      - FINERACT_DEFAULT_TENANTDB_CONN_PARAMS=
+      - FINERACT_DEFAULT_TENANTDB_TIMEZONE=Asia/Kolkata
+      - FINERACT_DEFAULT_TENANTDB_IDENTIFIER=default
+      - FINERACT_DEFAULT_TENANTDB_NAME=fineract_default
+      - FINERACT_DEFAULT_TENANTDB_DESCRIPTION=Default Demo Tenant
+      - JAVA_TOOL_OPTIONS="-Xmx1G"
diff --git a/custom/foo/build.gradle b/custom/foo/build.gradle
deleted file mode 100644
index 9b7233870..000000000
--- a/custom/foo/build.gradle
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-description = 'ACME Corp.: Fineract Foo'
diff --git a/custom/foo/service/dependencies.gradle 
b/custom/foo/service/dependencies.gradle
deleted file mode 100644
index 17ee19c84..000000000
--- a/custom/foo/service/dependencies.gradle
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-dependencies {
-    implementation(project(':module:dummy:core'),
-            'org.springframework:spring-context')
-    testImplementation('io.cucumber:cucumber-spring')
-}
diff --git a/fineract-doc/src/docs/en/chapters/architecture/modules.adoc 
b/fineract-doc/src/docs/en/chapters/architecture/modules.adoc
index 9eb720043..50d63367b 100644
--- a/fineract-doc/src/docs/en/chapters/architecture/modules.adoc
+++ b/fineract-doc/src/docs/en/chapters/architecture/modules.adoc
@@ -1,250 +1,82 @@
 = Modules
 
-Currently modules are a proof of concept feature in Fineract.
+NOTE: Currently modules are a proof of concept feature in Fineract.
 
-== How to create a Fineract module
+== Custom modules
 
-NOTE: At the moment the only module we have that follows these stricter 
guidelines is the example module we describe in detail in the next section. In 
the future we will try to split up Fineract's monolithic code base and move 
features (like loan product, accounts etc.) to separate modules as described 
here. Before that happens we need to do some code cleanups first.
-
-1. Create a folder under `module` and name it _mymodule_.
-2. Create a folder named _core_ under `module/mymodule`.
-3. Create a folder named _service_ under `module/mymodule`.
-4. Create a folder named _starter_ under `module/mymodule`.
-5. Setup folders and Gradle build files for a Java project under `core`, 
`service` and `starter`.
-6. Create a text file called _spring.factories in folder 
`modules/mymodule/starter/src/main/resources/META-INF`.
-+
-The final folder and file structure should look somewhat like this:
-+
-[plantuml, format=svg]
-----
-include::{diagramsdir}/modules-folder-structure.puml[]
-----
+Creating customizations for Fineract services is easy. The method described 
here will work both with our future module guidelines (aka "clean room" 
modules) and with the intermediary solution we will put in place to avoid major 
refactorings.
 
-7. Make sure that your new modules are present in settings.gradle:
-+
-[source,groovy]
-----
-rootProject.name='fineract'
-include ':module:mymodule:core' // <.>
-include ':module:mymodule:service'
-include ':module:mymodule:starter'
-include ':fineract-provider'
-include ':fineract-war'
-include ':integration-tests'
-include ':twofactor-tests'
-include ':oauth2-tests'
-include ':fineract-client'
-include ':fineract-doc'
-----
-<.> The `settings.gradle` file should contain something like this (and 
following).
+The folder structure for modules is based on a convention that ensures that 
your extensions don't clash with Fineract's internals. This is to make sure 
that your downstream forks of Fineract are easy to sync. In the past we had all 
kinds of strategies to add custom code - including editing existing sources in 
`fineract-provider`. This is not recommended.
 
-== How to replace an existing Fineract service
+IMPORTANT: At the moment the only service(s) we prepared to be 
overridden/replaced are 
`org.apache.fineract.portfolio.note.service.NoteReadPlatformService` and 
`org.apache.fineract.portfolio.note.service.NoteWritePlatformService`. Please 
reach out on the developer mailing list if you need other services.
 
-Creating customizations for Fineract services is easy. The method described 
here will work both with our future module guidelines (aka "clean room" 
modules) and with the intermediary solution we will put in place to avoid major 
refactorings.
+The recommended folder structure is very simple. If you follow this 
recommendation you'll get some additional benefits, e. g. you don't even have 
to edit `settings.gradle` to include your new custom modules. Your modules will 
also be automatically included in a custom Fineract Docker image build that you 
can use for your production deployments.
 
-You can of course choose whatever folder/project structure you like for your 
custom modules. But we'll describe here some best practices to avoid merge 
conflicts when fetching updates from Fineract's upstream Git repository. For 
the time being we suggest to create your custom modules in the same folder as 
Fineract in a forked Git repo.
+Let's assume your company/org is called "ACME Inc." and you are trying to 
(fully/partially) replace an existing Fineract service, let's say those in 
`org.apache.fineract.portfolio.note`. The recommended folder structure would 
then look something like this:
 
-As soon as we can publish Fineract module JARs to Maven Central you'll have 
more freedom to setup your projects (including to setup separate Git repos).
-
-1. Create a folder under `custom` and name it _mycustom_.
-2. Create a folder named _service_ under `custom/mycustom`.
-3. Setup folders and Gradle build files for a Java project under `service`.
-4. Create a text file called _spring.factories in folder 
`modules/mymodule/starter/src/main/resources/META-INF`:
-+
-[source,properties]
-----
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- org.apache.fineract.mymodule.starter.MyAutoConfiguration
-----
-+
-The final folder and file structure should look somewhat like this:
-+
 [plantuml, format=svg]
 ----
 include::{diagramsdir}/custom-folder-structure.puml[]
 ----
-+
-Please make sure that your `service` module's build.gradle file has a unique 
group:
-+
-[source,groovy]
-----
-description = 'My Company: Fineract My Custom Service'
 
-group = 'com.mycompany.fineract.custom.service' // <.>
+As soon as we can publish Fineract module JARs to Maven Central you'll have 
more freedom to setup your projects (including to setup separate Git repos). 
But for now please follow these instructions:
 
-apply from: 'dependencies.gradle'
-----
-+
-<.> The best choice here is to name it like you would name your JAR artifact 
on Maven Central.
-
-5. Make sure that your custom modules are present in settings.gradle: :
+1. Create a folder under `custom` and name it according to your 
company/organisation (e. g. `acme` if your company is `ACME Inc.`); this way 
your custom modules can't clash even with other companies' modules
+2. Under your company folder create a folder for the `category` or `domain` 
your module is targeting; e. g. "loan", "client", "account" etc.
+3. Finally, setup `library` folders for the actual modules you want to create; 
usually that will be to replace/extend some existing service, so there could be 
a `service` folder, maybe even a `core` folder, e. g. if you want to add 
additional DTOs etc.; we have also an example for COB business steps
+4. Per `category`/`domain` you should have a `starter` library; means: a 
Spring Boot auto-configuration setup that makes including your module in 
Fineract easier ("hands-free"); the necessary parts for a auto-configuration 
library are a Spring Java configuration class (annotated with `@Configuration`) 
and a text file at `META-INF/spring.factories` in your starter resource folder:
 +
-[source,groovy]
-----
-rootProject.name='fineract'
-include ':custom:mycustom:service' // <.>
-include ':fineract-provider'
-include ':fineract-war'
-include ':integration-tests'
-include ':twofactor-tests'
-include ':oauth2-tests'
-include ':fineract-client'
-include ':fineract-doc'
-----
-<.> The `settings.gradle` file should contain something like this.
-6. The dependency.gradle file could look something like this:
-[source,groovy]
-----
-dependencies {
-    implementation(project(':module:mymodule:core'), // <.>
-            'org.springframework:spring-context')
-    compileOnly project(':fineract-provider') // <.>
-}
-----
-<.> If you are replacing a "clean room" module then you just need to include a 
dependency on the modules `core` library.
-<.> If you are replacing a service that is not yet extracted from 
`fineract-provider` then you need to add a dependency on it (only needed during 
compilation).
-1. When the custom module is built then you can add the JAR in your Fineract's 
libs folder (or in Tomcat's libs folder if you are deploying as a WAR app).
-
-IMPORTANT: Do not include your custom module in `fineract-provider`'s 
dependency.gradle file. This creates a circular dependency and will fail your 
build. Instead you have to add your JAR file e. g. to the Docker image (in 
Fineract's libs folder; similar like we do it with Pentaho reporting). This 
setup will give you the best developer experience for now with proper source 
references until we have separate JAR files ready on Maven Central. In the next 
section we describe the "clean room" [...]
-
-== Example
-
-=== Dummy Module Structure
-
-We've created a demonstration how modules are supposed to be used. A module 
has usually at least two sub-modules:
-
-1. a _core_ module that contains mostly Java interfaces of the services and/or 
other components that we'd like to make replacable. The _core_ module could 
also contain data (DTO) or domain (entity) classes if necessary.
-2. a _service_ module
-
-IMPORTANT: Replacing parts of the existing REST API and extending it with 
custom endpoints is at the moment out of scope. Probably we'll need to cleanup 
and improve the REST layer quite a bit (remove boilerplate code as much as 
possible, use Jackson for JSON de-/serialization instead of the manual GSON 
mappers/helper everywhere) before this can happen. For now we'll concentrate 
primarily on service classes.
-
-Let's assume we have a service `DummyService` in our system. The service is 
outlined as a Java interface in module folder `module/dummy/core`.
-
-[source,java]
-----
-include::{rootdir}/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/service/DummyService.java[lines=19..]
-----
-
-To make it a little bit more interesting the service's only function returns a 
simple data object that has one string attribute:
-
-[source,java]
-----
-include::{rootdir}/module/dummy/core/src/main/java/org/apache/fineract/dummy/core/data/DummyMessage.java[lines=19..]
-----
-
-A default implementation of `DummyService` is provided with `DummyServiceImpl`:
-
-[source,java]
-----
-include::{rootdir}/module/dummy/service/src/main/java/org/apache/fineract/dummy/service/DummyServiceImpl.java[lines=19..]
-----
-
-As you can see there are no annotations like `@Service` or `@Component`. To 
have full control over the instantiation and dependency injection we provide a 
so called _starter_ module. This module just contains one or more (auto-) 
configuration classes. In this case it's just one configuration class. This is 
basically a very simple Spring Java configuration class annotated with 
`@Configuration` containing one method that instantiates our default service 
implementation (annotated with `@Bean`):
-
-[source,java]
-----
-include::{rootdir}/module/dummy/starter/src/main/java/org/apache/fineract/dummy/starter/DummyAutoConfiguration.java[lines=19..]
-----
-
-The interesting part is this line:
-
-[source,java]
-----
-include::{rootdir}/module/dummy/starter/src/main/java/org/apache/fineract/dummy/starter/DummyAutoConfiguration.java[lines=28..28]
-----
-
-This annotation ensures that our default implementation is only instantiated 
if no other implementation for `DummyService` is provided. There's only one 
piece missing to make auto configuration work seamlessly and without any 
explicit configuration in `fineract-provider`:
-
 [source,properties]
 ----
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- org.apache.fineract.dummy.starter.DummyAutoConfiguration
+include::{rootdir}/custom/acme/note/starter/src/main/resources/META-INF/spring.factories[]
 ----
-
-This means if we would only include the following dependencies in our main 
project (e. g. `fineract-provider`)...
-
++
+Please make sure that your module libraries have proper `build.gradle` files:
++
 [source,groovy]
 ----
-dependencies {
-    implementation project(':module:dummy:core')
-    implementation project(':module:dummy:service')
-    implementation project(':module:dummy:starter')
-}
-----
-
-\... then the message object would contain
-
-[source,text]
-----
-Hello: DEFAULT DUMMY!
-----
-
-=== Replace the default implementation of DummyService
-
-This is why we created module `foo` in folder `module/foo`. There's only only 
one sub-module that contains an implementation of `DummyService` that will 
replace the default implementation:
-
-[source,java]
-----
-include::{rootdir}/custom/foo/service/src/main/java/com/acmecorp/fineract/foo/service/FooDummyServiceImpl.java[lines=19..]
+include::{rootdir}/custom/acme/note/service/build.gradle[lines=19..]
 ----
-
-As you can see we have no other sub-modules (e. g. there's no starter module) 
and this implementation contains the well known `@Service` annotation. If we 
add this dependency to our main project...
-
++
+NOTE: You don't need to edit `settings.gradle` to add your modules/libraries. 
If you follow above convention they'll get included automatically.
++
+5. The dependency.gradle file could look something like this:
 [source,groovy]
 ----
-dependencies {
-    implementation project(':module:dummy:core')
-    implementation project(':module:dummy:service')
-    implementation project(':module:dummy:starter')
-    implementation project(':custom:foo:service')
-}
+include::{rootdir}/custom/acme/note/service/dependencies.gradle[lines=19..]
 ----
 
-\... then the expected behavior is that we'll get the message
-
-[source,text]
-----
-Hello: Foo!
-----
+NOTE: We've included by default some basic and useful dependencies for all 
custom modules, like Slf4j, Lombok, the usual testing frameworks (JUnit, 
Cucumber, Mockito etc.)
 
-=== Testing
+IMPORTANT: Do not include your custom module in `fineract-provider`'s 
dependency.gradle file. This creates a circular dependency and will fail your 
build.
 
-There's a simple unit test in `fineract-provider` that demonstrates both 
scenarios (without any override of the default implementation and with an 
overriding alternative implementation)
+=== Database Migrations
 
-[source,gherkin]
-----
-include::{rootdir}/fineract-provider/src/test/resources/features/module/modules.example.feature[]
-----
+If database migrations are needed as part of your customizations then you can 
add your own migration scripts. This is again based on conventions:
 
-Here's the Spring Java configuration that does *NOT* load the `foo` module:
+1. Create folders `db/custom-changelog` in one of your `resources` folders; we 
recommend using the resources folder in your starter library, but actually any 
of your custom libs will do.
+2. Under `db/custom-changelog` create an XML changelog file, e. g. 
`changelog-acme-note.xml`; you are free to choose a name for this file, but we 
recommend being consistent to avoid classpath conflicts.
+3. Under `db/custom-changelog` create a folder `parts` for your specific 
changelogs
 
-[source,java]
+[plantuml, format=svg]
 ----
-include::{rootdir}/fineract-provider/src/test/java/org/apache/fineract/module/example/TestDefaultConfiguration.java[lines=19..]
+include::{diagramsdir}/custom-db-migration-folder-structure.puml[]
 ----
 
-\... and here's the configuration that loads the `foo` module (and overrides 
the deefault implementation)
-
-[source,java]
-----
-include::{rootdir}/fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java[lines=19..]
-----
+=== Deployment
 
-The important part in the `foo` configuration is this line:
+Custom modules (better: the JAR files) only need to be dropped in Fineract's 
`libs` folder if you run Fineract from the Spring Boot JAR file. Dynamic 
loading of external JARs is provided since Fineract version 1.5.0. For your 
convenience we've created a separate Docker image module that automatically 
includes your custom modules (see `custom/docker`). You can build this Docker 
image with
 
-[source,java]
+[source,bash]
 ----
-include::{rootdir}/fineract-provider/src/test/java/org/apache/fineract/module/example/TestFooConfiguration.java[lines=26..26]
+./gradlew :custom:docker:jibDockerBuild
 ----
 
-=== Deployment
-
-Modules (better: the JAR files) only need to be dropped in Fineract's `libs` 
folder. Dynamic loading of external JARs is provided since Fineract version 
1.5.0.
+The Docker image with included custom modules is called `fineract-custom`.
 
-=== How can I start replacing services now
+NOTE: We'll provide soon a way to customize the Docker image parameters (image 
name, JVM implementation, JVM args, ports etc.).
 
-As said the "clean room" modules will take a while to arrive. In the meanwhile 
we'll prepare the existing monolithic code base for pluggability. As a proof of 
concept the services 
`org.apache.fineract.portfolio.note.service.NoteReadPlatformService` and 
`org.apache.fineract.portfolio.note.service.NoteWritePlatformService` can be 
replaced/overriden by custom implementations. We'll add more shortly and will 
list them here. Reach out on the mailing list if you need a specific service to 
be r [...]
 
 === Outlook
 
-If this proof of concept is accepted we could refactor Fineract's services one 
by one and rearrange them in the proposed module structure. As discussed at 
ApacheCon 2021 there will be most likely some work to do to properly extract 
those modules (loan product, client, savings account etc.) from the monolithic 
code base. The main challenge are cross-dependencies between the _modules_, but 
there are major benefits if we split up the code like this. One - as 
demonstrated - is the replacemen [...]
+If this *proof of concept* is accepted we could prepare Fineract's services to 
be replaceable. This approach works already very well even if we don't have 
proper JAR libraries published on Maven Central. It's an important goal to 
separate customized code from Fineract's internals to have soon real modules.
diff --git 
a/fineract-doc/src/docs/en/diagrams/custom-db-migration-folder-structure.puml 
b/fineract-doc/src/docs/en/diagrams/custom-db-migration-folder-structure.puml
new file mode 100644
index 000000000..17b2c8cf6
--- /dev/null
+++ 
b/fineract-doc/src/docs/en/diagrams/custom-db-migration-folder-structure.puml
@@ -0,0 +1,25 @@
+@startsalt
+{
+    {T
+    + custom
+    ++ acme
+    +++ note
+    ++++ starter
+    +++++ src
+    ++++++ main
+    +++++++ java
+    ++++++++ com.acme.fineract.note.starter
+    +++++++++ AcmeNoteAutoConfiguration.java
+    +++++ resources
+    ++++++ db
+    +++++++ custom-changelog
+    ++++++++ parts
+    +++++++++ 0001_acme_note_initial.xml
+    ++++++++ changelog-acme-note.xml
+    ++++++ META-INF
+    +++++++ spring.factories
+    +++++ build.gradle
+    +++++ dependencies.gradle
+    }
+}
+@endsalt
\ No newline at end of file
diff --git a/fineract-doc/src/docs/en/diagrams/custom-folder-structure.puml 
b/fineract-doc/src/docs/en/diagrams/custom-folder-structure.puml
index bc9b3b577..b0a73ae2a 100644
--- a/fineract-doc/src/docs/en/diagrams/custom-folder-structure.puml
+++ b/fineract-doc/src/docs/en/diagrams/custom-folder-structure.puml
@@ -2,16 +2,39 @@
 {
     {T
     + custom
-    ++ mycustom
-    +++ service
-    ++++ src
-    +++++ main
-    ++++++ java
-    +++++++ org.apache.fineract.mymodule.service
-    ++++++++ MyServiceImpl.java
-    ++++ build.gradle
-    ++++ dependencies.gradle
-    +++ build.gradle
+    ++ acme
+    +++ note
+    ++++ core
+    +++++ src
+    ++++++ main
+    +++++++ java
+    ++++++++ com.acme.fineract.note.core
+    +++++++++ data
+    ++++++++++ MyData.java
+    +++++++++ service
+    ++++++++++ MyCustomService.java
+    +++++ build.gradle
+    +++++ dependencies.gradle
+    ++++ service
+    +++++ src
+    ++++++ main
+    +++++++ java
+    ++++++++ com.acme.fineract.note.service
+    +++++++++ AcmeNoteReadService.java
+    +++++++++ AcmeNoteWriteService.java
+    +++++ build.gradle
+    +++++ dependencies.gradle
+    ++++ starter
+    +++++ src
+    ++++++ main
+    +++++++ java
+    ++++++++ com.acme.fineract.note.starter
+    +++++++++ AcmeNoteAutoConfiguration.java
+    +++++ resources
+    ++++++ META-INF
+    +++++++ spring.factories
+    +++++ build.gradle
+    +++++ dependencies.gradle
     }
 }
 @endsalt
\ No newline at end of file
diff --git a/fineract-doc/src/docs/en/diagrams/modules-folder-structure.puml 
b/fineract-doc/src/docs/en/diagrams/modules-folder-structure.puml
deleted file mode 100644
index f7a61dd35..000000000
--- a/fineract-doc/src/docs/en/diagrams/modules-folder-structure.puml
+++ /dev/null
@@ -1,39 +0,0 @@
-@startsalt
-{
-    {T
-    + module
-    ++ mymodule
-    +++ core
-    ++++ src
-    +++++ main
-    ++++++ java
-    +++++++ org.apache.fineract.mymodule.core
-    ++++++++ data
-    +++++++++ MyData.java
-    ++++++++ service
-    +++++++++ MyService.java
-    ++++ build.gradle
-    ++++ dependencies.gradle
-    +++ service
-    ++++ src
-    +++++ main
-    ++++++ java
-    +++++++ org.apache.fineract.mymodule.service
-    ++++++++ MyServiceImpl.java
-    ++++ build.gradle
-    ++++ dependencies.gradle
-    +++ starter
-    ++++ src
-    +++++ main
-    ++++++ java
-    +++++++ org.apache.fineract.mymodule.starter
-    ++++++++ MyAutoConfiguration.java
-    ++++ resources
-    +++++ META-INF
-    ++++++ spring.factories
-    ++++ build.gradle
-    ++++ dependencies.gradle
-    +++ build.gradle
-    }
-}
-@endsalt
\ No newline at end of file
diff --git a/fineract-provider/dependencies.gradle 
b/fineract-provider/dependencies.gradle
index e09bf3e92..07952b14e 100644
--- a/fineract-provider/dependencies.gradle
+++ b/fineract-provider/dependencies.gradle
@@ -161,10 +161,6 @@ dependencies {
     //
     testImplementation( 'io.cucumber:cucumber-spring',
             'io.github.classgraph:classgraph',
-            project(':module:dummy:core'),
-            project(':module:dummy:service'),
-            project(':module:dummy:starter'),
-            project(':custom:foo:service'),
             )
     testImplementation ('org.springframework.boot:spring-boot-starter-test') {
         exclude group: 'com.jayway.jsonpath', module: 'json-path'
@@ -173,6 +169,6 @@ dependencies {
         exclude group: 'javax.activation'
         exclude group: 'org.skyscreamer'
     }
-    testImplementation ('org.mockito:mockito-inline:4.8.0')
+    testImplementation ('org.mockito:mockito-inline')
 
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java
index ed7e15cdc..ca76bf94a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java
@@ -45,7 +45,7 @@ public class ShareProductDividendAssembler {
     private final ShareAccountReadPlatformService 
shareAccountReadPlatformService;
 
     @Autowired
-    public ShareProductDividendAssembler(final 
ShareProductReadPlatformServiceImpl shareProductReadPlatformService,
+    public ShareProductDividendAssembler(final ShareProductReadPlatformService 
shareProductReadPlatformService,
             final ShareAccountReadPlatformService 
shareAccountReadPlatformService) {
         this.shareProductReadPlatformService = shareProductReadPlatformService;
         this.shareAccountReadPlatformService = shareAccountReadPlatformService;
diff --git 
a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml 
b/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
index 5bc9e3cd3..9f9c3b880 100644
--- a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
+++ b/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
@@ -29,4 +29,5 @@
     <include file="tenant-store/changelog-tenant-store.xml" 
relativeToChangelogFile="true" context="tenant_store_db AND !initial_switch"/>
     <include file="tenant/initial-switch-changelog-tenant.xml" 
relativeToChangelogFile="true" context="tenant_db AND initial_switch"/>
     <include file="tenant/changelog-tenant.xml" relativeToChangelogFile="true" 
context="tenant_db AND !initial_switch"/>
+    <includeAll path="db/custom-changelog" errorIfMissingOrEmpty="false" />
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/module/example/DummyServiceStepDefinitions.java
 
b/fineract-provider/src/test/java/org/apache/fineract/module/example/DummyServiceStepDefinitions.java
deleted file mode 100644
index ccbacd785..000000000
--- 
a/fineract-provider/src/test/java/org/apache/fineract/module/example/DummyServiceStepDefinitions.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.module.example;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import io.cucumber.java8.En;
-import org.apache.fineract.dummy.core.data.DummyMessage;
-import org.apache.fineract.dummy.core.service.DummyService;
-import org.apache.fineract.dummy.starter.DummyAutoConfiguration;
-import org.springframework.boot.autoconfigure.AutoConfigurations;
-import org.springframework.boot.test.context.runner.ApplicationContextRunner;
-
-public class DummyServiceStepDefinitions implements En {
-
-    private DummyMessage message;
-
-    private ApplicationContextRunner contextRunner = new 
ApplicationContextRunner()
-            
.withConfiguration(AutoConfigurations.of(DummyAutoConfiguration.class));
-
-    public DummyServiceStepDefinitions() {
-        Given("/^A dummy service configuration (.*)$/", (String 
configurationClass) -> {
-            contextRunner = 
contextRunner.withUserConfiguration(Class.forName(configurationClass.trim()));
-        });
-
-        When("The user gets the dummy service message", () -> {
-            contextRunner.run((ctx) -> {
-                assertThat(ctx).hasSingleBean(DummyService.class);
-                this.message = ctx.getBean(DummyService.class).getMessage();
-            });
-        });
-
-        Then("/^The dummy service message should match (.*)$/", (String msg) 
-> {
-            assertThat(this.message).isNotNull();
-            assertThat(msg).isEqualTo(this.message.getMessage());
-        });
-    }
-}
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/module/example/TestDefaultConfiguration.java
 
b/fineract-provider/src/test/java/org/apache/fineract/module/example/TestDefaultConfiguration.java
deleted file mode 100644
index 6978660ad..000000000
--- 
a/fineract-provider/src/test/java/org/apache/fineract/module/example/TestDefaultConfiguration.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.module.example;
-
-import org.apache.fineract.infrastructure.core.config.FineractProperties;
-import 
org.springframework.boot.context.properties.EnableConfigurationProperties;
-
-@EnableConfigurationProperties({ FineractProperties.class })
-public class TestDefaultConfiguration {}
diff --git a/module/build.gradle b/module/build.gradle
deleted file mode 100644
index d1454985c..000000000
--- a/module/build.gradle
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-description = 'Fineract Module'
diff --git a/module/dummy/build.gradle b/module/dummy/build.gradle
deleted file mode 100644
index 016d5b2c3..000000000
--- a/module/dummy/build.gradle
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-description = 'Fineract Module: Dummy'
diff --git 
a/module/dummy/service/src/main/java/org/apache/fineract/dummy/service/DummyServiceImpl.java
 
b/module/dummy/service/src/main/java/org/apache/fineract/dummy/service/DummyServiceImpl.java
deleted file mode 100644
index 5445c8b83..000000000
--- 
a/module/dummy/service/src/main/java/org/apache/fineract/dummy/service/DummyServiceImpl.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.dummy.service;
-
-import org.apache.fineract.dummy.core.data.DummyMessage;
-import org.apache.fineract.dummy.core.service.DummyService;
-
-public class DummyServiceImpl implements DummyService {
-
-    @Override
-    public DummyMessage getMessage() {
-        return new DummyMessage("Hello: DEFAULT DUMMY!");
-    }
-}
diff --git 
a/module/dummy/starter/src/main/java/org/apache/fineract/dummy/starter/DummyAutoConfiguration.java
 
b/module/dummy/starter/src/main/java/org/apache/fineract/dummy/starter/DummyAutoConfiguration.java
deleted file mode 100644
index ffcd1be6d..000000000
--- 
a/module/dummy/starter/src/main/java/org/apache/fineract/dummy/starter/DummyAutoConfiguration.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.dummy.starter;
-
-import org.apache.fineract.dummy.core.service.DummyService;
-import org.apache.fineract.dummy.service.DummyServiceImpl;
-import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-@ConditionalOnMissingBean(DummyService.class)
-public class DummyAutoConfiguration {
-
-    @Bean
-    public DummyService dummyService() {
-        return new DummyServiceImpl();
-    }
-}
diff --git a/settings.gradle b/settings.gradle
index 078473734..6d87daf20 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -17,10 +17,6 @@
  * under the License.
  */
 rootProject.name='fineract'
-include ':module:dummy:core'
-include ':module:dummy:service'
-include ':module:dummy:starter'
-include ':custom:foo:service'
 include ':fineract-provider'
 include ':fineract-war'
 include ':integration-tests'
@@ -29,3 +25,19 @@ include ':oauth2-tests'
 include ':fineract-client'
 include ':fineract-doc'
 include ':fineract-avro-schemas'
+// NOTE: custom Docker image with all custom modules included
+include ':custom:docker'
+// NOTE: dynamically load custom modules with pattern "custom -> company -> 
category -> module"
+file("${rootDir}/custom").eachDir { companyDir ->
+    if('build' != companyDir.name && 'docker' != companyDir.name) {
+        file("${rootDir}/custom/${companyDir.name}").eachDir { categoryDir ->
+            if('build' != categoryDir.name) {
+                
file("${rootDir}/custom/${companyDir.name}/${categoryDir.name}").eachDir { 
moduleDir ->
+                    if('build' != moduleDir.name) {
+                        include 
":custom:${companyDir.name}:${categoryDir.name}:${moduleDir.name}"
+                    }
+                }
+            }
+        }
+    }
+}

Reply via email to