This is an automated email from the ASF dual-hosted git repository.
aleks pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/fineract-site.git
The following commit(s) were added to refs/heads/asf-site by this push:
new c82ad36 chore: Publish current docs
c82ad36 is described below
commit c82ad36339e92a800c3061f04372ae550b641bbb
Author: Aleksandar Vidakovic <[email protected]>
AuthorDate: Fri Sep 16 00:57:16 2022 +0200
chore: Publish current docs
---
docs/current/index.html | 1075 ++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 1012 insertions(+), 63 deletions(-)
diff --git a/docs/current/index.html b/docs/current/index.html
index 7fcf988..acc4d37 100644
--- a/docs/current/index.html
+++ b/docs/current/index.html
@@ -530,7 +530,7 @@ table.CodeRay td.code{padding:0 0 0 .75em}
<div id="header">
<h1>Fineract Platform Documentation</h1>
<div class="details">
-<span id="revnumber">version 0.0.0-d003326e</span>
+<span id="revnumber">version 0.0.0-a313bced</span>
</div>
<div id="toc" class="toc2">
<div id="toctitle">Table of Contents</div>
@@ -573,6 +573,7 @@ table.CodeRay td.code{padding:0 0 0 .75em}
<li><a href="#_technology">Technology</a></li>
<li><a href="#_modules">Modules</a></li>
<li><a
href="#_introducing_business_date_into_fineract_community_version">Introducing
Business Date into Fineract - Community version</a></li>
+<li><a href="#_reliable_event_framework">Reliable event framework</a></li>
</ul>
</li>
<li><a href="#_fineract_development_environment">Fineract Development
Environment</a>
@@ -675,7 +676,7 @@ Website: <a href="https://fineract.apache.org"
class="bare">fineract.apache.org<
Email: <a
href="mailto:[email protected]">[email protected]</a></p>
</div>
<div class="paragraph">
-<p><strong>Version</strong>: 0.0.0-d003326e</p>
+<p><strong>Version</strong>: 0.0.0-a313bced</p>
</div>
<div class="paragraph">
<p><strong>Date</strong>: 2022-09-09</p>
@@ -1702,7 +1703,7 @@ The implementation of the platform code to process
commands through handlers whi
<span class="directive">final</span> <span
class="predefined-type">String</span> json = wrapper.getJson();
CommandProcessingResult result = <span
class="predefined-constant">null</span>;
- JsonCommand command = <span class="predefined-constant">null</span>;
+ JsonCommand command;
<span class="type">int</span> numberOfRetries = <span
class="integer">0</span>;
<span class="type">int</span> maxNumberOfRetries =
ThreadLocalContextUtil.getTenant().getConnection().getMaxRetriesOnDeadlock();
<span class="type">int</span> maxIntervalBetweenRetries =
ThreadLocalContextUtil.getTenant().getConnection().getMaxIntervalBetweenRetries();
@@ -1713,7 +1714,7 @@ The implementation of the platform code to process
commands through handlers whi
wrapper.getOrganisationCreditBureauId());
<span class="keyword">while</span> (numberOfRetries <=
maxNumberOfRetries) {
<span class="keyword">try</span> {
- result = <span
class="local-variable">this</span>.processAndLogCommandService.processAndLogCommand(wrapper,
command, isApprovedByChecker);
+ result = <span
class="local-variable">this</span>.processAndLogCommandService.executeCommand(wrapper,
command, isApprovedByChecker);
numberOfRetries = maxNumberOfRetries + <span
class="integer">1</span>;
} <span class="keyword">catch</span> (CannotAcquireLockException |
ObjectOptimisticLockingFailureException exception) {
log.info(<span class="string"><span
class="delimiter">"</span><span class="content">The following command {}
has been retried {} time(s)</span><span
class="delimiter">"</span></span>, command.json(), numberOfRetries);
@@ -1757,11 +1758,9 @@ The implementation of the platform code to process
commands through handlers whi
<div class="listingblock">
<div class="title">Check user has permission for this action. if ok, a) parse
the json request body, b) create a JsonCommand object to wrap the command
details, c) use CommandProcessingService to handle command</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="java"> <span
class="directive">final</span> AppUser maker = <span
class="local-variable">this</span>.context.authenticatedUser(wrapper);
-
- CommandSource commandSourceResult;
+<pre class="CodeRay highlight"><code data-lang="java"> CommandSource
commandSourceResult;
<span class="keyword">if</span> (command.commandId() != <span
class="predefined-constant">null</span>) {
- commandSourceResult = <span
class="local-variable">this</span>.commandSourceRepository.findById(command.commandId())
+ commandSourceResult =
commandSourceRepository.findById(command.commandId())
.orElseThrow(() -> <span class="keyword">new</span>
CommandNotFoundException(command.commandId()));
commandSourceResult.markAsChecked(maker);
} <span class="keyword">else</span> {
@@ -1774,7 +1773,7 @@ The implementation of the platform code to process
commands through handlers whi
<span class="predefined-type">String</span> changesOnlyJson;
<span class="type">boolean</span> rollBack = (rollbackTransaction ||
result.isRollbackTransaction()) && !isApprovedByChecker;
<span class="keyword">if</span> (result.hasChanges() &&
!rollBack) {
- changesOnlyJson = <span
class="local-variable">this</span>.toApiJsonSerializer.serializeResult(result.getChanges());
+ changesOnlyJson =
toApiJsonSerializer.serializeResult(result.getChanges());
commandSourceResult.updateJsonTo(changesOnlyJson);
}
@@ -1783,7 +1782,7 @@ The implementation of the platform code to process
commands through handlers whi
}
<span class="keyword">if</span> (commandSourceResult.hasJson()) {
- <span
class="local-variable">this</span>.commandSourceRepository.save(commandSourceResult);
+ commandSourceRepository.save(commandSourceResult);
}
<span class="keyword">if</span> ((rollbackTransaction ||
result.isRollbackTransaction()) && !isApprovedByChecker) {
@@ -1801,7 +1800,7 @@ The implementation of the platform code to process
commands through handlers whi
}
result.setRollbackTransaction(<span
class="predefined-constant">null</span>);
- publishEvent(wrapper.entityName(), wrapper.actionName(), command,
result);
+ publishHookEvent(wrapper.entityName(), wrapper.actionName(), command,
result);
<span class="keyword">return</span> result;
}
@@ -1811,7 +1810,7 @@ The implementation of the platform code to process
commands through handlers whi
<span class="directive">public</span> CommandProcessingResult
logCommand(CommandSource commandSourceResult) {
commandSourceResult.markAsAwaitingApproval();
- commandSourceResult = <span
class="local-variable">this</span>.commandSourceRepository.saveAndFlush(commandSourceResult);
+ commandSourceResult =
commandSourceRepository.saveAndFlush(commandSourceResult);
<span class="keyword">return</span> <span class="keyword">new</span>
CommandProcessingResultBuilder().withCommandId(commandSourceResult.getId())
.withEntityId(commandSourceResult.getResourceId()).build();
@@ -1822,8 +1821,9 @@ The implementation of the platform code to process
commands through handlers whi
<span class="keyword">if</span> (wrapper.isDatatableResource()) {
<span class="keyword">if</span> (wrapper.isCreateDatatable()) {
- handler = <span
class="local-variable">this</span>.applicationContext.getBean(<span
class="string"><span class="delimiter">"</span><span
class="content">createDatatableCommandHandler</span><span
class="delimiter">"</span></span>, NewCommandSourceHandler.class);
- } <span class="keyword">else</span> <span
class="keyword">if</span> (wrapper.isDeleteDatatable()) {</code></pre>
+ handler = applicationContext.getBean(<span
class="string"><span class="delimiter">"</span><span
class="content">createDatatableCommandHandler</span><span
class="delimiter">"</span></span>, NewCommandSourceHandler.class);
+ } <span class="keyword">else</span> <span
class="keyword">if</span> (wrapper.isDeleteDatatable()) {
+ handler = applicationContext.getBean(<span
class="string"><span class="delimiter">"</span><span
class="content">deleteDatatableCommandHandler</span><span
class="delimiter">"</span></span>,
NewCommandSourceHandler.class);</code></pre>
</div>
</div>
<div class="admonitionblock note">
@@ -3530,6 +3530,948 @@ Feature: Example Modules
</div>
</div>
</div>
+<div class="sect2">
+<h3 id="_reliable_event_framework">Reliable event framework</h3>
+<div class="paragraph">
+<p>Fineract is capable of generating and raising events for external consumers
in a reliable way. This section is going to describe all the details on that
front with examples.</p>
+</div>
+<div class="sect3">
+<h4 id="_framework_capabilities">Framework capabilities</h4>
+<div class="sect4">
+<h5 id="_acid_transactional_guarantee">ACID (transactional) guarantee</h5>
+<div class="paragraph">
+<p>The event framework must support ACID guarantees on the business operation
level.</p>
+</div>
+<div class="paragraph">
+<p>Let’s see a simple use-case:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>A client applies to a loan on the UI</p>
+</li>
+<li>
+<p>The loan is created on the server</p>
+</li>
+<li>
+<p>A loan creation event is raised</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>What happens if step 3 fails? Shall it fail the original loan creation
process?</p>
+</div>
+<div class="paragraph">
+<p>What happens if step 2 fails but step 3 still gets executed? We’re raising
an event for a loan that hasn’t been created in reality.</p>
+</div>
+<div class="paragraph">
+<p>Therefore, raising an event is tied to the original business transaction to
ensure the data that’s getting written into the database along with the
respective events are saved in an all-or-nothing fashion.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_messaging_integration">Messaging integration</h5>
+<div class="paragraph">
+<p>The system is able to send the raised events to downstream message
channels. The current implementation supports the following message
channels:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>ActiveMQ</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_ordering_guarantee">Ordering guarantee</h5>
+<div class="paragraph">
+<p>The events that are raised will be sent to the downstream message channels
in the same order as they were raised.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_delivery_guarantee">Delivery guarantee</h5>
+<div class="paragraph">
+<p>The framework supports the at-least-once delivery guarantee for the raised
events.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_reliability_and_fault_tolerance">Reliability and fault-tolerance</h5>
+<div class="paragraph">
+<p>In terms of reliability and fault-tolerance, the event framework is able to
handle the cases when the downstream message channel is not able to accept
events. As soon as the message channel is back to operational, the events will
be sent again.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_selective_event_producing">Selective event producing</h5>
+<div class="paragraph">
+<p>Whether or not an event must be sent to downstream message channels for a
particular Fineract instance is configurable through the UI and API.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_standardized_format">Standardized format</h5>
+<div class="paragraph">
+<p>All the events sent to downstream message channels are conforming a
standardized format using Avro schemas.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_extendability_and_customizations">Extendability and
customizations</h5>
+<div class="paragraph">
+<p>The event framework is capable of being easily extended with new events for
additional business operations or customizing existing events.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_ability_to_send_events_in_bulk">Ability to send events in bulk</h5>
+<div class="paragraph">
+<p>The event framework makes it possible to sort of queue events until they
are ready to be sent and send them as a single message instead of sending each
event as a separate, individual one.</p>
+</div>
+<div class="paragraph">
+<p>For example during the COB process, there might be events raised in
separate business steps which needs to be sent out but they only need to be
sent out at the end of the COB execution process instead of one-by-one.</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_architecture_2">Architecture</h4>
+<div class="sect4">
+<h5 id="_intro">Intro</h5>
+<div class="paragraph">
+<p>On a high-level, the concept looks the following. An event gets raised in a
business operation. The event data gets saved to the database - to ensure ACID
guarantees. An asynchronous process takes the saved events from the database
and puts them onto a message channel.</p>
+</div>
+<div class="paragraph">
+<p>The flow can be seen in the following diagram:</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAtMAAAGyCAYAAAAmtEhTAAAAAXNSR0IArs4c6QAAIABJREFUeF7svQu4FtV5/n2DCoIKKEQRKirtFzCtoUFjNMBHo0ZIJIlCKuYD/okxkSiEiAEPyEkOxooB5UMNRmIaoBUbjIkYhRgtBeKhSIv2UjAtKh5C0o0ixI2H6P5fz7ifN2svZt6Zeec8c891bWXvmbXWs37Peue955lnrdUOPEiABEiABEiABEiABEiABBoi0K6hUixEAiRAAiRAAiRAAiRAAiQAimkOAhIgARIgARIgARIgARJokADFdIPgWIwESIAESIAESIAESIAEKKY5BkiABEiABEiABEiABEigQQIU0w2CYzESIAESIAESIAESIAESoJjmGCABEiABEiABEiABEiCBBglQTDcIjsVIgARIgARIgARIgARIgGKaY4AESIAESIAE
[...]
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_foundational_business_events">Foundational business events</h5>
+<div class="paragraph">
+<p>The whole framework is built upon an existing infrastructure in Fineract;
the Business Events.</p>
+</div>
+<div class="paragraph">
+<p>As a quick recap, Business Events are Fineract events that can be raised at
any place in a business operation using the
<code>BusinessEventNotifierService</code>. Callbacks can be registered when a
certain type of Business Event is raised and other business operations can be
done. For example when a Loan gets disbursed, there’s an interested party
doing the Loan Arrears Aging recalculation using the Business Event
communication.</p>
+</div>
+<div class="paragraph">
+<p>The nice thing about the Business Events is that they are tied to the
original transaction which means if any of the processing on the
subscriber’s side fail, the entire original transaction will be rolled
back. This was one of the requirements for the Reliable event framework.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_event_database_integration">Event database integration</h5>
+<div class="paragraph">
+<p>The database plays a crucial part in the framework since to ensure
transactionality, - without doing proper transaction synchronization between
different message channels and the database - the framework is going to save
all the raised events into the same relational database that Fineract is
using.</p>
+</div>
+<div class="sect5">
+<h6 id="_database_structure">Database structure</h6>
+<div class="paragraph">
+<p>The database structure looks the following</p>
+</div>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><strong>Name</strong></p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><strong>Type</strong></p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><strong>Description</strong></p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><strong>Example</strong></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>id</code></p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">number</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Auto
incremented ID.<br>
+<br>
+Not null.</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>1</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>type</code></p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">text</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The event
type as a string.<br>
+<br>
+Not null.</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>LoanApprovedBusinessEvent</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>schema</code></p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">text</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The fully
qualified name of the schema that was used for the data serialization, as a
string.<br>
+<br>
+Not null.</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>org.apache.fineract.avro.loan.v1.LoanAccountDataV1</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>data</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">BLOB
(MySQL/MariaDB), BYTEA (PostgreSQL)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The event
payload as Avro binary.<br>
+<br>
+Not null.</p></td>
+<td class="tableblock halign-left valign-top"></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>created_at</code></p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">timestamp</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">UTC
timestamp when the event was raised.<br>
+<br>
+Not null.</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>2022-09-06 14:20:10.148627 +00:00</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>status</code></p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">text</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Enum text
representing the status of the external event.<br>
+<br>
+Not null, indexed.</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>TO_BE_SENT</code>, <code>SENT</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>sent_at</code></p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">timestamp</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">UTC
timestamp when the event was sent.</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>2022-09-06 14:30:10.148627 +00:00</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>idempotency_key</code></p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">text</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Randomly
generated UUID upon inserting a row into the table for idempotency purposes.<br>
+<br>
+Not null.</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>68aed085-8235-4722-b27d-b38674c19445</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>business_date</code></p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">date</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The
business date to when the event was generated.<br>
+<br>
+Not null, indexed.</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>2022-09-05</code></p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>The above database table contains the unsent events which later on will be
sent by an asynchronous event processor.</p>
+</div>
+<div class="paragraph">
+<p>Upon successfully sending an event, the corresponding statuses will be
updated.</p>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_avro_schemas">Avro schemas</h5>
+<div class="paragraph">
+<p>For serializing events, Fineract is using Apache Avro. There are 2 reasons
for that:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>More compact storage since Avro is a binary format</p>
+</li>
+<li>
+<p>The Avro schemas are published with Fineract as a separate JAR so event
consumers can directly map the events into POJOs</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>There are 3 different levels of Avro schemas used in Fineract for the
Reliable event framework which are described below.</p>
+</div>
+<div class="sect5">
+<h6 id="_standard_event_schema">Standard event schema</h6>
+<div class="paragraph">
+<p>The standard event schema is for the regular events. These schemas are used
when saving a raised event into the database and using the Avro schema to
serialize the event data into a binary format.</p>
+</div>
+<div class="paragraph">
+<p>For example the OfficeDataV1 Avro schema looks the following:</p>
+</div>
+<details>
+<summary class="title"><code>OfficeDataV1.avsc</code></summary>
+<div class="content">
+<div class="listingblock">
+<div class="content">
+<pre class="CodeRay highlight"><code data-lang="avroschema">{
+ "name": "OfficeDataV1",
+ "namespace": "org.apache.fineract.avro.office.v1",
+ "type": "record",
+ "fields": [
+ {
+ "default": null,
+ "name": "id",
+ "type": [
+ "null",
+ "long"
+ ]
+ },
+ {
+ "default": null,
+ "name": "name",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ {
+ "default": null,
+ "name": "nameDecorated",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ {
+ "default": null,
+ "name": "externalId",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ {
+ "default": null,
+ "name": "openingDate",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ {
+ "default": null,
+ "name": "hierarchy",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ {
+ "default": null,
+ "name": "parentId",
+ "type": [
+ "null",
+ "long"
+ ]
+ },
+ {
+ "default": null,
+ "name": "parentName",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ {
+ "default": null,
+ "name": "allowedParents",
+ "type": [
+ "null",
+ {
+ "type": "array",
+ "items":
"org.apache.fineract.avro.office.v1.OfficeDataV1"
+ }
+ ]
+ }
+ ]
+}</code></pre>
+</div>
+</div>
+</div>
+</details>
+</div>
+<div class="sect5">
+<h6 id="_event_message_schema">Event message schema</h6>
+<div class="paragraph">
+<p>The event message schema is just a wrapper around the standard event schema
with extra metadata for the event consumers.</p>
+</div>
+<div class="paragraph">
+<p>Since Avro is strongly typed, the event content needs to be first
serialized into a byte sequence and that needs to be wrapped around.</p>
+</div>
+<div class="paragraph">
+<p>This implies that for putting a single event message onto a message queue
for external consumption, data needs to be serialized 2 times; this is the
2-level serialization.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Serializing the event</p>
+</li>
+<li>
+<p>Serializing the already serialized event into an event message using the
message wrapper</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>The message schema looks the following:</p>
+</div>
+<div class="listingblock">
+<div class="title"><code>MessageV1.avsc</code></div>
+<div class="content">
+<pre class="CodeRay highlight"><code data-lang="avroschema">{
+ "name": "MessageV1",
+ "namespace": "org.apache.fineract.avro",
+ "type": "record",
+ "fields": [
+ {
+ "name": "id",
+ "doc": "The ID of the message to be sent",
+ "type": "int"
+ },
+ {
+ "name": "source",
+ "doc": "A unique identifier of the source
service",
+ "type": "string"
+ },
+ {
+ "name": "type",
+ "doc": "The type of event the payload refers to.
For example LoanApprovedBusinessEvent",
+ "type": "string"
+ },
+ {
+ "name": "category",
+ "doc": "The category of event the payload refers
to. For example LOAN",
+ "type": "string"
+ },
+ {
+ "name": "createdAt",
+ "doc": "The UTC time of when the event has been
raised; in ISO_LOCAL_DATE_TIME format. For example 2011-12-03T10:15:30",
+ "type": "string"
+ },
+ {
+ "name": "tenantId",
+ "doc": "The tenantId that the event has been sent
from. For example default",
+ "type": "string"
+ },
+ {
+ "name": "idempotencyKey",
+ "doc": "The idempotency key for this particular
event for consumer de-duplication",
+ "type": "string"
+ },
+ {
+ "name": "dataschema",
+ "doc": "The fully qualified name of the schema of
the event payload. For example
org.apache.fineract.avro.loan.v1.LoanAccountDataV1",
+ "type": "string"
+ },
+ {
+ "name": "data",
+ "doc": "The payload data serialized into Avro
bytes",
+ "type": "bytes"
+ }
+ ]
+}</code></pre>
+</div>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_bulk_event_schema">Bulk event schema</h6>
+<div class="paragraph">
+<p>The bulk event schema is used when multiple events are supposed to be sent
together. This schema is used also when serializing the data for the database
storing but the idea is quite simple. Have an array of other event schemas
embedded into it.</p>
+</div>
+<div class="paragraph">
+<p>Since Avro is strongly typed, the array within the bulk event schema is an
array of <code>MessageV1</code> schemas. That way the consumers can decide
which events they want to deserialize and which don’t.</p>
+</div>
+<div class="paragraph">
+<p>This elevates the regular 2-level serialization/deserialization concept up
to a 3-level one:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Serializing the standard events</p>
+</li>
+<li>
+<p>Serializing the standard events into a bulk event</p>
+</li>
+<li>
+<p>Serializing the bulk event into an event message</p>
+</li>
+</ol>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_versioning">Versioning</h6>
+<div class="paragraph">
+<p>Avro is quite strict with changes to an existing schema and there are a
number of compatibility modes available.</p>
+</div>
+<div class="paragraph">
+<p>Fineract keeps it simple though. Version numbers - in the package names and
in the schema names - are increased with each published modification; meaning
that if the OfficeDataV1 schema needs a new field and the
<code>OfficeDataV1</code> schema has been published officially with Fineract, a
new <code>OfficeDataV2</code> has to be created with the new field instead of
modifying the existing schema.</p>
+</div>
+<div class="paragraph">
+<p>This pattern ensures that a certain event is always deserialized with the
appropriate schema definition, otherwise the deserialization could fail.</p>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_code_generation">Code generation</h6>
+<div class="paragraph">
+<p>The Avro schemas are described as JSON documents. That’s hardly
usable directly with Java hence Fineract generates Java POJOs from the Avro
schemas. The good thing about these POJOs is the fact that they can be
serialized/deserialized in themselves without any magic since they have a
<code>toByteBuffer</code> and <code>fromByteBuffer</code> method.</p>
+</div>
+<div class="paragraph">
+<p>From POJO to ByteBuffer:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="CodeRay highlight"><code data-lang="java">LoanAccountDataV1
avroDto = ...
+ByteBuffer buffer = avroDto.toByteBuffer();</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>From ByteBuffer to POJO:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="CodeRay highlight"><code data-lang="java"><span
class="predefined-type">ByteBuffer</span> buffer = ...
+LoanAccountDataV1 avroDto =
LoanAccountDataV1.fromByteBuffer(buffer);</code></pre>
+</div>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+The ByteBuffer is a stateful container and needs to be handled carefully.
Therefore Fineract has a built-in ByteBuffer to byte array converter;
<code>ByteBufferConverter</code>.
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_downstream_event_consumption">Downstream event consumption</h6>
+<div class="paragraph">
+<p>When consuming events on the other side of the message channel, it’s
critical to know which events the system is interested in. With the multi-level
serialization, it’s possible to deserialize only parts of the message and
decide based on that whether it makes sense for a particular system to
deserialize the event payload more.</p>
+</div>
+<div class="paragraph">
+<p>Whether events are important can be decided based on:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>the <code>type</code> attribute in the message</p>
+</li>
+<li>
+<p>the <code>category</code> attribute in the message</p>
+</li>
+<li>
+<p>the <code>dataschema</code> attribute in the message</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>These are the main attributes in the message wrapper one can use to decide
whether an event message is useful.</p>
+</div>
+<div class="paragraph">
+<p>If the event needs to be deserialized, the next step is to find the
corresponding schema definition. That’s going to be sent in the
<code>dataschema</code> attribute within the message wrapper. Since the
attribute contains the fully-qualified name of the respective schema, it can be
easily resolved to a Class object. Based on that class, the payload data can be
easily deserialized using the <code>fromByteBuffer</code> method on every
generated schema POJO.</p>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_message_ordering">Message ordering</h5>
+<div class="paragraph">
+<p>One of the requirements for the framework is to provide ordering
guarantees. All the events have to conform a happens-before relation.</p>
+</div>
+<div class="paragraph">
+<p>For the downstream consumers, this can be verified by the <code>id</code>
attribute within the messages. Since it’s going to be a
strictly-monotonic numeric sequence, it can be used for ordering purposes.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_event_categorization">Event categorization</h5>
+<div class="paragraph">
+<p>For easier consumption, the terminology event category is introduced. This
is nothing else but the bounded context an event is related to.</p>
+</div>
+<div class="paragraph">
+<p>For example the LoanApprovedBusinessEvent and the
LoanWaiveInterestBusinessEvent are both related to the Loan bounded
contexts.</p>
+</div>
+<div class="paragraph">
+<p>The category in which an event resides in is included in the message under
the <code>category</code> attribute.</p>
+</div>
+<div class="paragraph">
+<p>The existing event categories can be found under the <a
href="#event-categories">Event categories</a> section.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_asynchronous_event_processor">Asynchronous event processor</h5>
+<div class="paragraph">
+<p>The events stored in the database will be picked up and sent by a regularly
executed job.</p>
+</div>
+<div class="paragraph">
+<p>This job is a Fineract job, scheduled to run for every minute and will pick
a number of events in order. Those events will be put onto the downstream
message channel in the same order as they were raised.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_purging_events">Purging events</h5>
+<div class="paragraph">
+<p>The events database table is going to grow continuously. That’s why
Fineract has a purging functionality in place that’s gonna delete old and
already sent events.</p>
+</div>
+<div class="paragraph">
+<p>It’s implemented as a Fineract job and is disabled by default.
It’s called TBD.</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_usage">Usage</h4>
+<div class="paragraph">
+<p>Using the event framework is quite simple. First, it has to be enabled
through properties or environment variable.</p>
+</div>
+<div class="paragraph">
+<p>The respective options are the following:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>the <code>fineract.events.external.enabled</code> property</p>
+</li>
+<li>
+<p>the <code>FINERACT_EXTERNAL_EVENTS_ENABLED</code> environment variable</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>These configurations accept a boolean value; <code>true</code> or
<code>false</code>.</p>
+</div>
+<div class="paragraph">
+<p>The key component to interact with is the
<code>BusinessEventNotifierService#notifyPostBusinessEvent</code> method.</p>
+</div>
+<div class="sect4">
+<h5 id="_raising_events">Raising events</h5>
+<div class="paragraph">
+<p>Raising events is really easy. An instance of a BusinessEvent interface is
needed, that’s going to be the event. There are plenty of them available
already in the Fineract codebase.</p>
+</div>
+<div class="paragraph">
+<p>And that’s pretty much it. Everything else is taken care of in terms
of event data persisting and later on putting it onto a message channel.</p>
+</div>
+<div class="paragraph">
+<p>An example of event raising:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="CodeRay highlight"><code data-lang="java"><span
class="annotation">@Override</span>
+<span class="directive">public</span> CommandProcessingResult
createClient(<span class="directive">final</span> JsonCommand command) {
+ ...
+ businessEventNotifierService.notifyPostBusinessEvent(<span
class="keyword">new</span> ClientCreateBusinessEvent(newClient));
+ ...
+ return ...;
+}</code></pre>
+</div>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+The above code is copied from the
<code>ClientWritePlatformServiceJpaRepositoryImpl</code> class.
+</td>
+</tr>
+</table>
+</div>
+<div class="sect5">
+<h6 id="_example_event_message_content">Example event message content</h6>
+<div class="paragraph">
+<p>Since the message is serialized into binary format, it’s hard to
represent in the documentation therefore here’s a JSON representation of
the data, just as an example.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="CodeRay highlight"><code data-lang="json">{
+ <span class="key"><span class="delimiter">"</span><span
class="content">id</span><span class="delimiter">"</span></span>: <span
class="integer">121</span>,
+ <span class="key"><span class="delimiter">"</span><span
class="content">source</span><span class="delimiter">"</span></span>:
<span class="string"><span class="delimiter">"</span><span
class="content">a65d759d-04f9-4ddf-ac52-34fa5d1f5a25</span><span
class="delimiter">"</span></span>,
+ <span class="key"><span class="delimiter">"</span><span
class="content">type</span><span class="delimiter">"</span></span>: <span
class="string"><span class="delimiter">"</span><span
class="content">LoanApprovedBusinessEvent</span><span
class="delimiter">"</span></span>,
+ <span class="key"><span class="delimiter">"</span><span
class="content">category</span><span class="delimiter">"</span></span>:
<span class="string"><span class="delimiter">"</span><span
class="content">Loan</span><span class="delimiter">"</span></span>,
+ <span class="key"><span class="delimiter">"</span><span
class="content">createdAt</span><span class="delimiter">"</span></span>:
<span class="string"><span class="delimiter">"</span><span
class="content">2022-09-05T10:15:30</span><span
class="delimiter">"</span></span>,
+ <span class="key"><span class="delimiter">"</span><span
class="content">tenantId</span><span class="delimiter">"</span></span>:
<span class="string"><span class="delimiter">"</span><span
class="content">default</span><span class="delimiter">"</span></span>,
+ <span class="key"><span class="delimiter">"</span><span
class="content">idempotencyKey</span><span
class="delimiter">"</span></span>: <span class="string"><span
class="delimiter">"</span><span
class="content">abda146d-68b5-48ca-b527-16d2b7c5daef</span><span
class="delimiter">"</span></span>,
+ <span class="key"><span class="delimiter">"</span><span
class="content">dataschema</span><span class="delimiter">"</span></span>:
<span class="string"><span class="delimiter">"</span><span
class="content">org.apache.fineract.avro.loan.v1.LoanAccountDataV1</span><span
class="delimiter">"</span></span>,
+ <span class="key"><span class="delimiter">"</span><span
class="content">data</span><span class="delimiter">"</span></span>: <span
class="string"><span class="delimiter">"</span><span
class="content">...</span><span class="delimiter">"</span></span>
+}</code></pre>
+</div>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+The source attribute refers to an ID that’s identifying the producer
service. Fineract will regenerate this ID upon each application startup.
+</td>
+</tr>
+</table>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_raising_bulk_events">Raising bulk events</h5>
+<div class="paragraph">
+<p>Raising bulk events is really easy as well. The 2 key methods are:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><code>BusinessEventNotifierService#startExternalEventRecording</code></p>
+</li>
+<li>
+<p><code>BusinessEventNotifierService#stopExternalEventRecording</code></p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>First, you have to start recording your events. This recording will be
applied for the current thread. And then you can raise as many events as you
want with the regular
<code>BusinessEventNotifierService#notifyPostBusinessEvent</code> method, but
they won’t get saved to the database immediately. They’ll get
"recorded" into an internal buffer.</p>
+</div>
+<div class="paragraph">
+<p>When you stop recording using the method above, all the recorded events
will be saved as a bulk event to the database; and serialized appropriately.</p>
+</div>
+<div class="paragraph">
+<p>From then on, the bulk event works just like any of the event. It’ll
be picked up by the processor to send it to a message channel.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="event-categories">Event categories</h5>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_selective_event_producing_2">Selective event producing</h5>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_customizations">Customizations</h4>
+<div class="paragraph">
+<p>The framework provides a number of customization options:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Creating new events (that’s already given by the Business Events)</p>
+</li>
+<li>
+<p>Creating new Avro schemas</p>
+</li>
+<li>
+<p>Customizing what data gets serialized for existing events</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>In the upcoming sections, that’s what going to be discussed.</p>
+</div>
+<div class="sect4">
+<h5 id="_creating_new_events">Creating new events</h5>
+<div class="paragraph">
+<p>Creating new events is super easy. Just create an implementation of the
<code>BusinessEvent</code> interface and that’s it.</p>
+</div>
+<div class="paragraph">
+<p>From then on, you can raise those events in the system, although you
can’t publish them to an external message channel. If you have the event
framework enabled, it’s going to fail with not finding the appropriate
serializer for your business event.</p>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+There are existing serializers which might be able to handle your new event.
For example the <code>LoanBusinessEventSerializer</code> is capable of handling
all <code>LoanBusinessEvent</code> subclasses so there’s no need to
create a brand new serializer.
+</td>
+</tr>
+</table>
+</div>
+<div class="paragraph">
+<p>The interface looks the following:</p>
+</div>
+<div class="listingblock">
+<div class="title"><code>BusinessEvent.java</code></div>
+<div class="content">
+<pre class="CodeRay highlight"><code data-lang="java"><span
class="directive">public</span> <span class="type">interface</span> <span
class="class">BusinessEvent</span><T> {
+
+ T get();
+
+ <span class="predefined-type">String</span> getType();
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Quite simple. The <code>get</code> method should return the data you want
to pass within the event instance. The <code>getType</code> method returns the
name of the business event that’s gonna be saved as the <code>type</code>
into the database.</p>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+Creating a new business event only means that it can be used for raising an
event. To make it compatible with the event framework and to be sent to a
message channel, some extra work is needed which are described below.
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_creating_new_avro_schemas_and_serializers">Creating new Avro schemas
and serializers</h5>
+<div class="paragraph">
+<p>First let’s talk about the event serializers because that’s
what’s needed to make a new event compatible with the framework.</p>
+</div>
+<div class="paragraph">
+<p>The serializer has a special interface,
<code>BusinessEventSerializer</code>.</p>
+</div>
+<div class="listingblock">
+<div class="title"><code>BusinessEventSerializer.java</code></div>
+<div class="content">
+<pre class="CodeRay highlight"><code data-lang="java"><span
class="directive">public</span> <span class="type">interface</span> <span
class="class">BusinessEventSerializer</span> {
+
+ <T> <span class="type">boolean</span>
canSerialize(BusinessEvent<T> event);
+
+ <T> <span class="type">byte</span><span class="type">[]</span>
serialize(BusinessEvent<T> rawEvent) <span
class="directive">throws</span> <span class="exception">IOException</span>;
+
+ <span class="predefined-type">Class</span><? <span
class="directive">extends</span> GenericContainer> getSupportedSchema();
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>An implementation of this interface shall be registered as a Spring bean,
and it’ll be picked up automatically by the framework.</p>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+You can look at the existing serializers for implementation ideas.
+</td>
+</tr>
+</table>
+</div>
+<div class="paragraph">
+<p>New Avro schemas can be easily created. Just create a new Avro schema file
in the <code>fineract-avro-schemas</code> project under the respective bounded
context folder, and it will be picked up automatically by the code
generator.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_bigdecimal_support_in_avro_schemas">BigDecimal support in Avro
schemas</h5>
+<div class="paragraph">
+<p>Apache Avro by default doesn’t support complex types like a
BigDecimal. It has to be implemented using a custom snippet like this:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="CodeRay highlight"><code data-lang="avroschema">{
+ "logicalType": "decimal",
+ "precision": 20,
+ "scale": 8,
+ "type": "bytes"
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>It’s a 20 precision and 8 scale BigDecimal.</p>
+</div>
+<div class="paragraph">
+<p>Obviously it’s quite challenging to copy-paste this snippet to every
single BigDecimal field, so there’s a customization in place for
Fineract.<br>
+The type <code>bigdecimal</code> is supported natively, and you’re free
to use it like this:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="CodeRay highlight"><code data-lang="avroschema">{
+ "default": null,
+ "name": "principal",
+ "type": [
+ "null",
+ "bigdecimal"
+ ]
+}</code></pre>
+</div>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+This <code>bigdecimal</code> type will be simple replaced with the BigDecimal
snippet showed above during the compilation process.
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_custom_data_serialization_for_existing_events">Custom data
serialization for existing events</h5>
+<div class="paragraph">
+<p>In case there’s a need some extra bit of information within the event
message that the default serializers are not providing, you can override this
behavior by registering a brand-new custom serializer (as shown above).</p>
+</div>
+<div class="paragraph">
+<p>Since there’s a priority order of serializers, the only thing the
custom serializer need to do is to be annotated by the <code>@Order</code>
annotation or to implement the <code>Ordered</code> interface.</p>
+</div>
+<div class="paragraph">
+<p>An example custom serializer with priority looks the following:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="CodeRay highlight"><code data-lang="java"><span
class="annotation">@Component</span>
+<span class="annotation">@RequiredArgsConstructor</span>
+<span class="annotation">@Order</span>(Ordered.HIGHEST_PRECEDENCE)
+<span class="directive">public</span> <span class="type">class</span> <span
class="class">CustomLoanBusinessEventSerializer</span> <span
class="directive">implements</span> BusinessEventSerializer {
+ ...
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <T> <span
class="type">boolean</span> canSerialize(BusinessEvent<T> event) {
+ <span class="keyword">return</span> ...;
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <T> <span
class="type">byte</span><span class="type">[]</span>
serialize(BusinessEvent<T> rawEvent) <span
class="directive">throws</span> <span class="exception">IOException</span> {
+ ...
+ ByteBuffer buffer = avroDto.toByteBuffer();
+ <span class="keyword">return</span>
byteBufferConverter.convert(buffer);
+ }
+
+ <span class="annotation">@Override</span>
+ <span class="directive">public</span> <span
class="predefined-type">Class</span><? <span
class="directive">extends</span> GenericContainer> getSupportedSchema() {
+ <span class="keyword">return</span> ...;
+ }
+}</code></pre>
+</div>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+All the default serializers are having <code>Ordered.LOWEST_PRECEDENCE</code>.
+</td>
+</tr>
+</table>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_appendix_a_properties_and_environment_variables">Appendix A:
Properties and environment variables</h4>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Property name</th>
+<th class="tableblock halign-left valign-top">Environment variable</th>
+<th class="tableblock halign-left valign-top">Default value</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>fineract.events.external.enabled</code></p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>FINERACT_EXTERNAL_EVENTS_ENABLED</code></p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>false</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Whether
the external event sending is enabled or disabled.</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
</div>
</div>
<div class="sect1">
@@ -5854,7 +6796,7 @@ ${project['fineract.config.name']}
<div class="listingblock">
<div class="title">Command</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep1 -Pfineract.release.issue=1234
-Pfineract.release.date="Monday, April 25, 2022"
-Pfineract.release.version=0.0.0-d003326e</code></pre>
+<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep1 -Pfineract.release.issue=1234
-Pfineract.release.date="Monday, April 25, 2022"
-Pfineract.release.version=0.0.0-a313bced</code></pre>
</div>
</div>
</div>
@@ -5876,7 +6818,7 @@ ${project['fineract.config.name']}
</div>
<div class="listingblock">
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="text">project = FINERACT and
fixVersion = 0.0.0-d003326e and status not in ( Resolved, Done, Accepted,
Closed )</code></pre>
+<pre class="CodeRay highlight"><code data-lang="text">project = FINERACT and
fixVersion = 0.0.0-a313bced and status not in ( Resolved, Done, Accepted,
Closed )</code></pre>
</div>
</div>
<div class="paragraph">
@@ -5884,7 +6826,7 @@ ${project['fineract.config.name']}
</div>
<div class="listingblock">
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="text">project = FINERACT and
fixVersion = 0.0.0-d003326e</code></pre>
+<pre class="CodeRay highlight"><code data-lang="text">project = FINERACT and
fixVersion = 0.0.0-a313bced</code></pre>
</div>
</div>
<div class="paragraph">
@@ -5896,7 +6838,7 @@ ${project['fineract.config.name']}
<div class="listingblock">
<div class="title">Command</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep2 -Pfineract.release.version=0.0.0-d003326e</code></pre>
+<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep2 -Pfineract.release.version=0.0.0-a313bced</code></pre>
</div>
</div>
<div class="admonitionblock caution">
@@ -5954,7 +6896,7 @@ This task is not yet automated!
<p>Create a new release branch with name "$Version"</p>
<div class="listingblock">
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="bash">% git checkout -b
0.0.0-d003326e</code></pre>
+<pre class="CodeRay highlight"><code data-lang="bash">% git checkout -b
0.0.0-a313bced</code></pre>
</div>
</div>
</li>
@@ -5962,7 +6904,7 @@ This task is not yet automated!
<p>Push new branch to Apache Fineract repository</p>
<div class="listingblock">
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="bash">% git push origin
0.0.0-d003326e</code></pre>
+<pre class="CodeRay highlight"><code data-lang="bash">% git push origin
0.0.0-a313bced</code></pre>
</div>
</div>
</li>
@@ -6001,7 +6943,7 @@ ${project['fineract.config.name']}</code></pre>
<div class="listingblock">
<div class="title">Command</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep3 -Pfineract.release.date="Monday, May 10, 2022"
-Pfineract.release.version=0.0.0-d003326e</code></pre>
+<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep3 -Pfineract.release.date="Monday, May 10, 2022"
-Pfineract.release.version=0.0.0-a313bced</code></pre>
</div>
</div>
</div>
@@ -6045,10 +6987,10 @@ This task is not yet automated!
</div>
<div class="listingblock">
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="bash">% git checkout
0.0.0-d003326e
+<pre class="CodeRay highlight"><code data-lang="bash">% git checkout
0.0.0-a313bced
% ./gradlew clean integrationTests <i class="conum"
data-value="1"></i><b>(1)</b>
-% git tag -a 0.0.0-d003326e -m "Fineract 0.0.0-d003326e release"
-% git push origin 0.0.0-d003326e</code></pre>
+% git tag -a 0.0.0-a313bced -m "Fineract 0.0.0-a313bced release"
+% git push origin 0.0.0-a313bced</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -6077,7 +7019,7 @@ It is important to create so called annotated tags (vs.
lightweight) for release
<div class="listingblock">
<div class="title">Command</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep5 -Pfineract.release.version=0.0.0-d003326e</code></pre>
+<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep5 -Pfineract.release.version=0.0.0-a313bced</code></pre>
</div>
</div>
</div>
@@ -6092,12 +7034,12 @@ It is important to create so called annotated tags (vs.
lightweight) for release
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code data-lang="bash">% cd
/fineract-release-preparations <i class="conum" data-value="1"></i><b>(1)</b>
-% tar -xvf apache-fineract-0.0.0-d003326e-src.tar.gz
+% tar -xvf apache-fineract-0.0.0-a313bced-src.tar.gz
% git clone <a href="https://git-wip-us.apache.org/repos/asf/fineract.git"
class="bare">git-wip-us.apache.org/repos/asf/fineract.git</a>
% cd fineract/
-% git checkout tags/0.0.0-d003326e
+% git checkout tags/0.0.0-a313bced
% cd ..
-% diff -r fineract apache-fineract-0.0.0-d003326e-src</code></pre>
+% diff -r fineract apache-fineract-0.0.0-a313bced-src</code></pre>
</div>
</div>
<div class="colist arabic">
@@ -6113,7 +7055,7 @@ It is important to create so called annotated tags (vs.
lightweight) for release
</div>
<div class="listingblock">
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="bash">% cd
apache-fineract-0.0.0-d003326e-src/fineract-provider <i class="conum"
data-value="1"></i><b>(1)</b>
+<pre class="CodeRay highlight"><code data-lang="bash">% cd
apache-fineract-0.0.0-a313bced-src/fineract-provider <i class="conum"
data-value="1"></i><b>(1)</b>
% gradlew clean integrationTest <i class="conum" data-value="2"></i><b>(2)</b>
% gradlew clean build <i class="conum" data-value="3"></i><b>(3)</b>
% gradlew rat <i class="conum" data-value="4"></i><b>(4)</b></code></pre>
@@ -6159,12 +7101,12 @@ It is important to create so called annotated tags (vs.
lightweight) for release
</div>
<div class="listingblock">
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="bash">% gpg --armor --output
apache-fineract-0.0.0-d003326e-src.tar.gz.asc --detach-sig
apache-fineract-0.0.0-d003326e-src.tar.gz
-% gpg --print-md MD5 apache-fineract-0.0.0-d003326e-src.tar.gz >
apache-fineract-0.0.0-d003326e-src.tar.gz.md5
-% gpg --print-md SHA512 apache-fineract-0.0.0-d003326e-src.tar.gz >
apache-fineract-0.0.0-d003326e-src.tar.gz.sha512
-% gpg --armor --output apache-fineract-0.0.0-d003326e--binary.tar.gz.asc
--detach-sig apache-fineract-0.0.0-d003326e-binary.tar.gz
-% gpg --print-md MD5 apache-fineract-0.0.0-d003326e-binary.tar.gz >
apache-fineract-0.0.0-d003326e-binary.tar.gz.md5
-% gpg --print-md SHA512 apache-fineract-0.0.0-d003326e-binary.tar.gz >
apache-fineract-0.0.0-d003326e-binary.tar.gz.sha512</code></pre>
+<pre class="CodeRay highlight"><code data-lang="bash">% gpg --armor --output
apache-fineract-0.0.0-a313bced-src.tar.gz.asc --detach-sig
apache-fineract-0.0.0-a313bced-src.tar.gz
+% gpg --print-md MD5 apache-fineract-0.0.0-a313bced-src.tar.gz >
apache-fineract-0.0.0-a313bced-src.tar.gz.md5
+% gpg --print-md SHA512 apache-fineract-0.0.0-a313bced-src.tar.gz >
apache-fineract-0.0.0-a313bced-src.tar.gz.sha512
+% gpg --armor --output apache-fineract-0.0.0-a313bced--binary.tar.gz.asc
--detach-sig apache-fineract-0.0.0-a313bced-binary.tar.gz
+% gpg --print-md MD5 apache-fineract-0.0.0-a313bced-binary.tar.gz >
apache-fineract-0.0.0-a313bced-binary.tar.gz.md5
+% gpg --print-md SHA512 apache-fineract-0.0.0-a313bced-binary.tar.gz >
apache-fineract-0.0.0-a313bced-binary.tar.gz.sha512</code></pre>
</div>
</div>
</div>
@@ -6183,33 +7125,33 @@ It is important to create so called annotated tags (vs.
lightweight) for release
<div class="sect4">
<h5 id="_description_8">Description</h5>
<div class="paragraph">
-<p>Finally create a directory with release name (0.0.0-d003326e in this
example) in <a href="https://dist.apache.org/repos/dist/dev/fineract"
class="bare">dist.apache.org/repos/dist/dev/fineract</a> and add the following
files in this new directory:</p>
+<p>Finally create a directory with release name (0.0.0-a313bced in this
example) in <a href="https://dist.apache.org/repos/dist/dev/fineract"
class="bare">dist.apache.org/repos/dist/dev/fineract</a> and add the following
files in this new directory:</p>
</div>
<div class="ulist">
<ul>
<li>
-<p>apache-fineract-0.0.0-d003326e-binary.tar.gz.sha</p>
+<p>apache-fineract-0.0.0-a313bced-binary.tar.gz.sha</p>
</li>
<li>
-<p>apache-fineract-0.0.0-d003326e-binary.tar.gz</p>
+<p>apache-fineract-0.0.0-a313bced-binary.tar.gz</p>
</li>
<li>
-<p>apache-fineract-0.0.0-d003326e-binary.tar.gz.asc</p>
+<p>apache-fineract-0.0.0-a313bced-binary.tar.gz.asc</p>
</li>
<li>
-<p>apache-fineract-0.0.0-d003326e-binary.tar.gz.md5</p>
+<p>apache-fineract-0.0.0-a313bced-binary.tar.gz.md5</p>
</li>
<li>
-<p>apache-fineract-0.0.0-d003326e-src.tar.gz.sha</p>
+<p>apache-fineract-0.0.0-a313bced-src.tar.gz.sha</p>
</li>
<li>
-<p>apache-fineract-0.0.0-d003326e-src.tar.gz</p>
+<p>apache-fineract-0.0.0-a313bced-src.tar.gz</p>
</li>
<li>
-<p>apache-fineract-0.0.0-d003326e-src.tar.gz.asc</p>
+<p>apache-fineract-0.0.0-a313bced-src.tar.gz.asc</p>
</li>
<li>
-<p>apache-fineract-0.0.0-d003326e-src.tar.gz.md5</p>
+<p>apache-fineract-0.0.0-a313bced-src.tar.gz.md5</p>
</li>
</ul>
</div>
@@ -6219,8 +7161,8 @@ It is important to create so called annotated tags (vs.
lightweight) for release
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code data-lang="bash">% svn co <a
href="https://dist.apache.org/repos/dist/dev/fineract/"
class="bare">dist.apache.org/repos/dist/dev/fineract/</a> fineract-dist-dev
-% mkdir fineract-dist-dev/0.0.0-d003326e
-% cp fineract/build/distributions/* fineract-dist-dev/0.0.0-d003326e/
+% mkdir fineract-dist-dev/0.0.0-a313bced
+% cp fineract/build/distributions/* fineract-dist-dev/0.0.0-a313bced/
% svn commit</code></pre>
</div>
</div>
@@ -6242,7 +7184,7 @@ You will need your ASF Committer credentials to be able
to access the Subversion
<div class="listingblock">
<div class="title">Command</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep8 -Pfineract.release.version=0.0.0-d003326e</code></pre>
+<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep8 -Pfineract.release.version=0.0.0-a313bced</code></pre>
</div>
</div>
</div>
@@ -6260,7 +7202,7 @@ You will need your ASF Committer credentials to be able
to access the Subversion
<div class="ulist">
<ul>
<li>
-<p>Release candidates should be in format
apache-fineract-0.0.0-d003326e-binary.tar.gz</p>
+<p>Release candidates should be in format
apache-fineract-0.0.0-a313bced-binary.tar.gz</p>
</li>
<li>
<p>Verify signatures and hashes. You may have to import the public key of the
release manager to verify the signatures. (<code>gpg --recv-key <key
id></code>)</p>
@@ -6291,7 +7233,7 @@ You will need your ASF Committer credentials to be able
to access the Subversion
<div class="listingblock">
<div class="title">Command</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep9 -Pfineract.release.version=0.0.0-d003326e</code></pre>
+<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep9 -Pfineract.release.version=0.0.0-a313bced</code></pre>
</div>
</div>
<div class="admonitionblock caution">
@@ -6350,7 +7292,7 @@ ${project['fineract.config.name']}</code></pre>
<div class="listingblock">
<div class="title">Command</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep10 -Pfineract.release.version=0.0.0-d003326e</code></pre>
+<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep10 -Pfineract.release.version=0.0.0-a313bced</code></pre>
</div>
</div>
</div>
@@ -6443,7 +7385,7 @@ ${project['fineract.config.name']}</code></pre>
<div class="listingblock">
<div class="title">Command</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="text">% ./gradlew
fineractReleaseStep11 -Pfineract.release.version=0.0.0-d003326e</code></pre>
+<pre class="CodeRay highlight"><code data-lang="text">% ./gradlew
fineractReleaseStep11 -Pfineract.release.version=0.0.0-a313bced</code></pre>
</div>
</div>
</div>
@@ -6458,10 +7400,10 @@ ${project['fineract.config.name']}</code></pre>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code data-lang="bash">% svn co <a
href="https://dist.apache.org/repos/dist/release/fineract"
class="bare">dist.apache.org/repos/dist/release/fineract</a> fineract-release
-% mkdir fineract-release/0.0.0-d003326e/
-% cp fineract-dist-dev/0.0.0-d003326e/* fineract-release/0.0.0-d003326e/
-% svn add fineract-release/0.0.0-d003326e/
-% svn commit -m "Fineract Release 0.0.0-d003326e"
fineract-release/0.0.0-d003326e/</code></pre>
+% mkdir fineract-release/0.0.0-a313bced/
+% cp fineract-dist-dev/0.0.0-a313bced/* fineract-release/0.0.0-a313bced/
+% svn add fineract-release/0.0.0-a313bced/
+% svn commit -m "Fineract Release 0.0.0-a313bced"
fineract-release/0.0.0-a313bced/</code></pre>
</div>
</div>
<div class="paragraph">
@@ -6473,7 +7415,7 @@ ${project['fineract.config.name']}</code></pre>
<div class="listingblock">
<div class="title">Command</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep12 -Pfineract.release.version=0.0.0-d003326e</code></pre>
+<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep12 -Pfineract.release.version=0.0.0-a313bced</code></pre>
</div>
</div>
</div>
@@ -6488,11 +7430,11 @@ ${project['fineract.config.name']}</code></pre>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code data-lang="bash">% git checkout develop
-% git branch -D 0.0.0-d003326e
-% git push origin :0.0.0-d003326e
+% git branch -D 0.0.0-a313bced
+% git push origin :0.0.0-a313bced
% git checkout develop
-% git checkout -b merge-0.0.0-d003326e
-% git merge -s recursive -Xignore-all-space 0.0.0-d003326e <i class="conum"
data-value="1"></i><b>(1)</b>
+% git checkout -b merge-0.0.0-a313bced
+% git merge -s recursive -Xignore-all-space 0.0.0-a313bced <i class="conum"
data-value="1"></i><b>(1)</b>
% git commit
% git push $USER
% hub pull-request</code></pre>
@@ -6512,7 +7454,7 @@ ${project['fineract.config.name']}</code></pre>
<div class="listingblock">
<div class="title">Command</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep13 -Pfineract.release.version=0.0.0-d003326e</code></pre>
+<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep13 -Pfineract.release.version=0.0.0-a313bced</code></pre>
</div>
</div>
<div class="admonitionblock caution">
@@ -6621,7 +7563,7 @@ The Apache Fineract Team</code></pre>
<div class="listingblock">
<div class="title">Command</div>
<div class="content">
-<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep15 -Pfineract.release.version=0.0.0-d003326e</code></pre>
+<pre class="CodeRay highlight"><code data-lang="bash">% ./gradlew
fineractReleaseStep15 -Pfineract.release.version=0.0.0-a313bced</code></pre>
</div>
</div>
</div>
@@ -116954,6 +117896,13 @@ Apache Fineract is a secure, multi-tenanted
microfinance platform. The goal of t
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">disallowExpectedDisbursements</p></td>
+<td class="tableblock halign-left valign-top"></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">Boolean</p></td>
+<td class="tableblock halign-left valign-top"></td>
+<td class="tableblock halign-left valign-top"></td>
+</tr>
+<tr>
<td class="tableblock halign-left valign-top"><p
class="tableblock">disbursementDetails</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Set of <a
href="#GetLoansLoanIdDisbursementDetails">[GetLoansLoanIdDisbursementDetails]</a></p></td>
@@ -170473,7 +171422,7 @@ Apache Fineract is a secure, multi-tenanted
microfinance platform. The goal of t
</div>
<div id="footer">
<div id="footer-text">
-Version 0.0.0-d003326e<br>
+Version 0.0.0-a313bced<br>
Last updated 2022-09-09 14:24:16 +0200
</div>
</div>