Skip to content
Erik Huelsmann edited this page Dec 27, 2020 · 4 revisions

Goal

The goal of the new Perl API is to be able to refactor the Perl code away from its current global-state-centric approach (which works for webapps) into a more generic library which can be used to develop e.g. command line applications or special-purpose extensions.

Elimination of current global state

Problematic classes

  • LedgerSMB::Sysconfig
    Currently holds the configuration for the entire Perl image.
    This class used to require a configuration file in the current directory - which is a big red flag for a library type of setup - but this problem has been addressed with recent changes.
  • LedgerSMB::App_State
    Holds in its global state the current database connection and the preferences for the currently logged in user. This setup is too restrictive in a library-setup, because a library might want to create multiple connections to the same or different companies.
  • LedgerSMB::Company_Config
    Holds the configuration of the currently connected company; similar to LedgerSMB::App_State, this restricts the library to one active connection.
    This functionality is a wrapper around LedgerSMB::Setting->get(), getting all settings at once.
  • LedgerSMB::PGObject
    This class (role) uses the global state of LedgerSMB::App_State and injects that in each of its derived classes. Other than that, it's a simple wrapper around PGObject::Simple::Role.
  • LedgerSMB::PGDate, LedgerSMB::PGNumber
    These classes depend on global state from LedgerSMB::App_State for formatting/parsing of input.
    Consensus has it that (user-)input parsing and (user-)output formatting belongs elsewhere; these classes serve as database-serializable representations of their in-memory parent classes

Problematic patterns

  1. The most problematic pattern is the pattern used by LedgerSMB::PGObject, because of the fact that it underlies a lot of classes and it affects all code instantiating any of these classes.

  2. The formatting/parsing pattern of LedgerSMB::PGDate and LedgerSMB::PGNumber looks like a major problem at first, but the grep statement output below indicates that the impact of a replacement should be manageable as all the occurrences in Scripts/ and old/ should have access to the request object (providing a means to pass a parser/formatter):

$ grep -Rc to_output lib old | grep -v ':0'
lib/LedgerSMB/Template.pm:2
lib/LedgerSMB/PGTimestamp.pm:3
lib/LedgerSMB/Report.pm:6
lib/LedgerSMB/Scripts/asset.pm:6
lib/LedgerSMB/Scripts/recon.pm:3
lib/LedgerSMB/Scripts/payment.pm:23
lib/LedgerSMB/Scripts/currency.pm:2
lib/LedgerSMB/PGDate.pm:3
lib/LedgerSMB/PGNumber.pm:2
lib/LedgerSMB/Report/Hierarchical.pm:2
lib/LedgerSMB/Report/PNL.pm:4
lib/LedgerSMB/Report/Balance_Sheet.pm:2
old/bin/gl.pl:1
old/bin/oe.pl:1
old/bin/ir.pl:1
old/bin/is.pl:1
old/bin/aa.pl:1
old/lib/LedgerSMB/Form.pm:1

$ grep -Rc from_input lib old | grep -v ':0'
lib/LedgerSMB/PGTimestamp.pm:3
lib/LedgerSMB/Scripts/asset.pm:3
lib/LedgerSMB/Scripts/recon.pm:2
lib/LedgerSMB/Scripts/business_unit.pm:2
lib/LedgerSMB/Scripts/payment.pm:24
lib/LedgerSMB/Scripts/budgets.pm:1
lib/LedgerSMB/Scripts/setup.pm:1
lib/LedgerSMB/Scripts/timecard.pm:5
lib/LedgerSMB/PGDate.pm:3
lib/LedgerSMB/MooseTypes.pm:6
lib/LedgerSMB/Reconciliation/StatementParser/CSV.pm:1
lib/LedgerSMB/PGNumber.pm:2
lib/LedgerSMB/Report/Dates.pm:2
old/bin/gl.pl:3
old/bin/oe.pl:1
old/bin/ir.pl:1
old/bin/is.pl:1
old/lib/LedgerSMB/IIAA.pm:1
old/lib/LedgerSMB/DBObject/Reconciliation.pm:5
old/lib/LedgerSMB/Form.pm:1
old/lib/LedgerSMB/AA.pm:1

Proposed alternative

The alternative is an API with a single entry point. This entry point is created with a pre-connected database and (run-context configuration; i.e. Sysconfig). This entry point propagates the database connection and configuration to objects that need it. This entry point provides access to all parts of the application through methods returning encapsulation of parts of the application (note that this does not need to coincide with class definitions):

  <main entry>
     -> me()            # connected user
        -> prefs()
     -> configuration() # company global configuration
        -> users()
        -> COANodes()
        -> GIFIs()
        -> sics()
        -> warehouses()
        -> settings()
     -> contacts()      # involved parties
     -> goods()         # goods & services (including inventory?)
     -> timecards()     # time cards
     -> sales()
        -> customers()  # ECAs!
        -> quotes()
        -> orders()
        -> transactions()
        -> shipments()
     -> purchases()
        -> (same)
...