... thank you for this post Arnold... I see that my favorite class (Loan)
grew from 6K LOC the last time I checked (which was not that long ago)...
;-)

I would even add another rule to what you said: if it's longer than 500 LOC
then something is wrong... ok, we know that real life is sometimes not that
binary, but as a rule of thumb I find that number helpful... anything that
is longer than that is really hard to digest. I am pretty sure if we look
closely at the details we'll see certain patterns emerging... there are
certainly a lot of if-then-elses... and each of those would be for me
something worth looking into for similarities and move them to separate
classes (e. g. loan schedule "strategies" in multiple classes instead of if
then elses in one service function). But this kind of refactoring comes
maybe last...

There's another code pattern that I think is responsible for a big chunk of
repetitive code: I think at one point the idea was to have immutable
classes/objects (e. g. data classes where attributes can only be set when
passing constructor values)... now, I don't want to dismiss this
entirely... I can see what the motivation for this is... but the rest of
the code requires us more often than not to copy the whole objects to
change a value; let's ignore the memory implications of that for a
moment... the "copy strategies" in Fineract are various... sometimes it's a
utility function inside the class itself, sometimes in a service, sometimes
the function is static... and so on... this kind of code alone doesn't make
it very easy to read the code; you are constantly side-tracked by this and
sent down some rabbit hole that in the could be a simple
"something.setValue(...)". I would let go of this and instead put Lombok in
place with access to all with proper getters and setters. This is very
simple and will help later (see next paragraph).

Mapping between classes is another topic here that is - my bet -
responsible for at least 20% of avoidable code. There are 2 kinds of
mappings in Fineract that are currently happening:

   - JSON mapping: there are some historic reasons why it's done the way we
   do it currently, mainly because of some irregular JSON structures that were
   hard to map automatically when Mifos/Fineract started. There are certain
   repeating patterns with helper classes (which is good), but occasionally we
   circumvented those and just did "open heart surgery" where we needed it.
      - The first issue I found when I found when I was investigating the
      dependencies between "modules" (well, it's more like packages at the
      moment) was that in the business logic classes you have
references e. g. to
      "org.apache.fineract.infrastructure.core.api.JsonQuery" which indicates
      that "belongs" to the REST API layer; down the trenches (read: in service
      classes) we should have all our ducks in a row, means everything properly
      mapped to domain classes.
      - We do a lot (if not all the mapping) with GSON; let's say, it's not
      the fastest kid in town... and not the most convenient. We were also not
      really consistent with its usage... somewhere we define quite a bunch of
      modules that help GSON de-/serializing certain non-standard structures
      (like Joda Time etc.); we should have done that too for our data
structures
      (data classes) instead of the JSON helpers we created. It's just too
      tempting to use the JSON helpers outside of their proper scope (which is
      the REST API layer).
      - Fineract in the end is a Spring/-Boot application which means we
      should replace GSON with Jackson (which is also faster and better
      integrated with Spring... again, less code). Anything that Jackson is not
      capable of to digest immediately (generic types come to mind) can be
      "fixed" with proper (Jackson) APIs (e. g.
      "com.fasterxml.jackson.databind.JsonDeserializer" and
      "com.fasterxml.jackson.databind.JsonSerializer"); these things are
      automatically managed by Spring, no manual anything. And we have exactly
      one place where the magic happens instead of interfering with it
constantly
      in the business logic.
   - data/domain mapping: those structures are often very similar, but
   because of the immutability thing I mentioned earlier it is very hard to
   translate back and forth. Let's imagine for a moment we had Lombok in
   place... then we could use Mapstruct (https://mapstruct.org) to generate
   all the code for getting and setting values for us; there would be still
   code, BUT:
      - it's generated (read: you don't have to maintain it)
      - structured 100% the same way (no code style differences)
      - debug friendly, because "real" code is generated (you can actually
      place breakpoints in Mapstruct's mappers)

The beauty of this approach is that we'll have immediately 2-way mapping;
in our current approach (I am paraphrasing) we have one helper function to
map from data to domain and another helper function from domain to data...
and there's hardly any reusable code in that area... so everyone who
introduces something new has to do this over and over again. With Mapstruct
you define in the best case an annotated simple Java interface and have 2
annotated method definitions. Even in more complex scenarios (that we will
have) it's a very structured approach and most importantly: things stay in
their place and are not moving everywhere.

Again... my bet... mapping will make 20% of the code base disappear.

These would be really worth attacking... I'm not saying easy, but it would
make life so much easier. And I am convinced we could do it in a way to
make it REST API backwards compatible (obviously internally Java APIs need
to change for this to happen).

Cheers,

Aleks



On Wed, Apr 6, 2022 at 10:20 AM Arnold Galovics <arn...@apache.org> wrote:

> Dear Fineract community,
>
> I've been thinking a lot about incoming PRs lately and how a lot of
> functionality is implemented around Loan management.
>
> One of the hardest things about Fineract's maintainability is the amount
> of code it includes. I previously proposed Lombok to reduce the boilerplate
> code therefore reducing the size of baggage we're carrying around; but
> that's just one step.
>
> Another thing we have to be really careful with is how to increase (or at
> least keep) the maintainability of the system while still adding new
> functionality to it.
> As I said in the beginning, Loan management. It's vital to Fineract and a
> lot of code is there to support all the use-cases.
> Just to give you some rough idea around LOC for some classes:
> - Loan class, ~7000 lines
> - LoanWritePlatformServiceJpaRepositoryImpl, ~3500 lines
> - LoanApplicationWritePlatformServiceJpaRepositoryImpl, ~1800 lines
> And this was just the tip of the iceberg.
>
> There's a lot more functionality to come. I feel like if those go into
> these classes, especially into the Loan class, what's already difficult to
> maintain will be impossible to maintain.
>
> I propose to restrict ourselves from adding more functionality to the Loan
> class (and to other classes which are just too big) and extracting those
> new functionalities into new, small classes which will be easy to deal
> with.
> And in the meantime, we can start breaking down these huge classes as well
> into smaller pieces without the fear that it'll grow faster than we could
> handle.
>
> Slowly but surely we can achieve an easily maintained, (mostly) bug free
> and most importantly extensible system that will bring joy not only into
> the life of financial institutions, but to the developers. ;-)
>
> Let me know your thoughts.
>
> Best,
> Arnold
>

Reply via email to