r20616-20630 (GncOwner)

John Ralls jralls at ceridwen.us
Sat May 14 22:34:10 EDT 2011


On May 14, 2011, at 4:33 PM, Phil Longstaff wrote:

> On Thu, 2011-05-12 at 13:49 -0400, John Ralls wrote:
> 
>> Geert, I was actually thinking from the data model view rather than the
>> accounting view, but ideally the data model closely reflects the
>> problem domain, so they're not dissimilar.
>> 
> [OpenERP - deleted]
> 
>> But more broadly, Gnucash is supposed to be an object-oriented design,
>> so that all of the common elements are provided by a superclass and
>> any additional specializations (like the pointer to a shipping address
>> for a customer) are provided by a subclass. (That single
>> characteristic is the fundamental flaw of the KVP design: Because KVP
>> data are added to objects outside of the class interface, inheritance
>> and polymorphism are broken.) The goal of the transactional
>> refactoring is that the GUI will be able to request from the backend
>> only the objects it needs to display, to lock only the objects it is
>> editing, to commit the changed edits as a logically atomic commit --
>> and to have the whole edit atomically rolled back if any part of the
>> commit fails. In order for that to work, objects must have control
>> over all of their member data and be able to serialize it to a
>> standard interface that an arbitrary backend can store and retrieve.
>> That was the original design goal stated for QO!  F, BTW, but
>> gnucash's implementation of QOF has destroyed that ability and
>> gnucash's use of QOF is inconsistent.
> 
> [ there are a bunch of GUIs - deleted ]
> 
>> Those two goals motivate my refactoring plan: Make everything in QOF
>> into a class hierarchy with separation of classes by functional and
>> data responsibility and all in the same class system (meaning GSEALED
>> GObject, C++, or Python -- each  of which has advantages, so which one
>> we use is open for discussion, and that discussion can be put off
>> until we have complete unit test coverage in QOF), then refactor
>> Engine, Business Core, and Backend into derived classes from the QOF
>> superclasses. That's the model part of MVC. After that (or in parallel
>> if someone else decides to work on it) we need to ensure that all of
>> the combining of those model objects for presentation to the user and
>> for handling edits (For example, posting an invoice will need to read
>> objects of the Customer, Invoice, Transaction, Split, Account, and
>> Commodity classes, with a write lock on Invoice, Transaction, and
>> Splits) is handled by another set of classes, so that the GUI and API
>> can concern themselves only !  with user interaction.  
> 
> This all sounds great and I'm wondering what the steps are.  One
> original idea behind KVP is that modules can be added to gnucash and can
> associate data with the core objects.  An example is a US tax data
> module which associates an account with a line on a tax form.  Should
> this tax line become part of the Account record in the db?  Might be OK
> for US users, but what if a user from a different country needs more
> information or different information.  Do we want Account, and
> USTaxAccount and OtherCountryTaxAccount derived from it?  What about
> other relationship types?
> 
> I think it would not be difficult to pull the info currently in slots
> out of them and into better data structures which have their own tables.
> They could be attached to the main core objects so that they load/save
> at the same time but would be independent and in their own tables.  Does
> this idea help with your refactoring?

The steps I have in mind are:
1. Get complete test coverage for QOF, Engine, Business Core, and Backend
2. Rewrite those libraries into a single, coherent class system (we'll discuss which one when the test-writing gets close to being done)
3. Refactor as necessary to get a good class hierarchy that reflects our acccounting model. This is the hard part, because we very likely keep different models in our heads, and we're going to have to work out how to express those models to each other and work out a common model that we all agree on, then document it so that we can refer to it as we refactor the code. This refactoring is very likely to involve moving parts of classes around, which is the reason for step 2: That's a real PITA if the classes aren't all written in the same "language".

There's more than one way to extend an object. Derivation is the one most people think of first. Another common pattern is a pointer to another class (hierarchy); depending on the semantics that you're trying to model, a pointer *from* another class can effectively extend a class, though that's likely to cause performance issues unless you also use signals (a common practice in Qt, Cocoa, and GObject, though not in mainstream C++ or Python). Note that registering a callback is a form of pointer-to-foreign-object. The important thing is that those pointers-to-foreign-objects are manipulated by class functions, not by foreign objects. That's where the KVP design breaks down: Instead of passing an object with a known interface to a class to store in that class's untyped "expansion data", KVP users are given a pointer to the expansion area and allowed to do whatever they like with it. 

Now we can continue to use KVP *in the XML backend* as a way to allow users to open newer-versioned files with old versions of the program (though it's not a common thing to do and I think it's pretty risky). The backend storage logic just needs to be able to figure out which KVP slots go to which core objects or plugin objects. It doesn't belong in the SQL backend, where its recursive design slows everything down and prevents the backend from enforcing referential integrity.

I'm not at all sure that the plugin architecture gets us anything in return for the added complexity, though. AFAIK there aren't any plugins. The various libraries in Gnucash proper that are dloaded instead of being dynamically linked sure doesn't get us anything except longer load times and missed optimization opportunities.

The other important point is that XML datasets and SQL databases have very different structures. Those differences are going to have to be abstracted away in the backend interface, but I think that can be done with a bit of refinement of your design on the SQL side. I haven't studied the XML side enough to know how much work will be involved, but I know how to write an XML backend and an object serialization interface to get us there. The general architecture has serialization/deserialization functions in the core classes which pass a tagged structure to the backend, similar to the structs you hard-coded into the various entity read/write modules in the SQL backends. Each backend maintains a mapping of the structures it knows how to store. Depending on how we decide to handle compatibility between versions, the backend may also have a place like KVP to put data that it doesn't know what to do with or it can raise an exception. One effect of this is that the "scrub" functions will move out of Engine into Backend.

Sorry, that's gotten really long-winded, and I might very well have added to the confusion. If I did, say so, and I'll try again in smaller chunks.

Regards,
John Ralls



More information about the gnucash-devel mailing list