GncBusiness v. GNCSession
Linas Vepstas
linas@linas.org
Wed, 21 Nov 2001 04:08:38 -0600
On Tue, Nov 20, 2001 at 01:52:46PM -0500, Derek Atkins was heard to remark:
> Linas,
>
> It feels as if we're talking past each other. :(
Hmmm. Don't feel bad. This is a complex topic. These things take time.
I think the conclusion to this email will be positive, although the
middle will be rocky.
> Right now, all the engine is stored as one big glop of code. I'd
> like to modularize it a bit more,
My first reaction is that I don't understand why you feel you need
to do this. But see below.
> in order to add more core objects
> to the engine in a dynamic manner. By "core object" I mean a basic
> accounting object, like an Account, a Customer, an Invoice...
Thats too vague for me. I cannot grasp the generalization you are
proposing by reading this sentance.
> > write now, gnucash doesn't have any structure that holds 'static
> > globals', not unless Dave snuck one in there while I wasn't looking.
>
> Yes it does. Take a look at gnc-module.c
? I don't understand. gnc-module doesn't seem to hold anything global.
Disclaimer: I have not looked at, studied, or have overheard a
description of the module system. So maybe I'm missing something.
But skimming the code, I don't see any place where I can dangle
one or more global structs. There's no hash table, no insert/delete
methods, no lookup/find methods.
> To give a better idea of what I mean, what I want to do is provide a
> registration where the core object C-code can register itself with the
> engine, so the engine knows that it exists. There is a structure:
>
> GncBusinessObject {
> int version;
> char * module_name;
> char * description;
> void (*destroy) ();
> GList * (*get_list) ();
> ...
> }
Well, at this level of description, how is a 'business object' different
than just another module? Why not just use a module to accomplish this?
again, given what you've written, I still do not grasp the abstraction
you are trying to make. But see below.
> Through this mechanisms, new object types can be entered into the
> system without changing any other code. My "search" widgets, for
> example, can choose ANY registered object. If I create a new object
> and register it, viola, I can now search it without any additional
> code changes!
? I can't begin to understand this claim. Say I want to search unpaid
invoices by date, or by amount, or by number of days left until its
due, and sort the results by the due date.
Maybe you could acheive this by extending src/engine/Query.c ...
Alternately you could do this with a very abstract ASN.1 type system
where you publish a MIB which says 'this is a field, its name is xyz,
and it stores a date' 'this is another field, its name is pqr, and
it stores a date interval'. And then you have a GUI that crawls
through the MIB and allows you to search based on dates and sort
by date intervals. But ... If you've ever actually used any MIB
Browsers ... yuck. They have a peculiarly unfilling, generic,
bland, boring, interface. There's no meaning, no semantics, no
semiotics. Sure, you can browse absolutely anything, but the GUI is
guarenteed to never be tuned for the actual task, a computerized
vacant stare.
> > > The second step is an Entity Table, and those
> > > belong in the Session.
> >
> > I don't know what an 'entity' is. The session table is really meant to
> > hold network connection things. Note that if there is no open
> > connection to a file or sql (or rpc) backend, there is no session.
> > (At least, thats how it used to work...)
>
> See GNCEntityTable in GNCIdP.h
Oh, sorry, I thought you were refering to some concept in the
business objects code.
> An entity is an instance of an object.
I think using language like this obscures the true meaning. An entity
is a 128-bit universally unique identifier. A unique name for the
an instance.
In terms of performance and efficiency, I think using pointers on the
local machine is always better than using guid's. But the GUID's do
serve as unique identifiers when referencing objects across the net.
I'm not sure what other people are using them for; for me, they are
interesting because they provide a way of storing, to persistant
storage, the value of a pointer. Damned Sucky Unix again: you can't
store pointers in unix files, you will never be able to get the same
pointer again. There are operating systems and/or languages that
don't have this problem. (as/400, and I'm told, smalltalk, and in a
sense, python, although I have not used python).
> > You can use gnucash without having a session: you just won't be able to
> > save your work until you do start a session.
>
> Can you? It certainly doesn't look that way to me. All the GUID
> tables are stored within a session. Without a session, you have no
> GUID tables, which means you cannot create any objects. So I
> disbelieve that Gnucash works without a session.
Ugh. After a long discussion with dave, he put the guid tables in the
session, rather than making them global, because this allowed a copy
operation. I don't quite remember why he picked session instead of
book. Again, something to do with the copy operation.
Dave? Can you remember why? Can I get you to add something to the docs
that says 'the reason that entity tables are in session not book is
because xyz ...'
> I agree that you don't need to have an open book!
Ugh. Since a book serves as the anchor for the account heirarchy, the
table of prices, etc, you can't create/add/delete data without an open
book. To manipulate data, you have to have a book.
Unless someone pulled another fast one on me that I don't understand.
This is what I mean: I don't think we've ever clearly articulated what
the difference between book and session is, what the correct way for
using them is, etc. This is a documentation/architecture problem.
It should really really be fixed.
> Besides, when the next person comes along and wants to add yet another
> core object, what are they supposed to do? A real core-object extension
> method would be best, IMHO.
You are assuming that you now know enough about what core objects are
like to be able to make the correct abstraction. I am not so sure.
So far, the engine has two core objects: the account tree, and the
price table. They are quite different. Looking at them, I cannot
really deduce a pattern.
Don't over-engineer the thing. Use the smallest amount of code to
get the job done, but no less. Lots of code just means lots of things
that are hard to grasp, more apis that need to be learned, to be
understood. Is the amount of function that they provide really
worth trying to learn and understand them?
To put it more parochially: why should I try to learn and understand
what a 'core object' is, when all that you seem to really need is an
opaque pointer in GNCBook? One line of code, one minute of coding,
versus days to design a generic mechanism. You don't have a half-dozen
core objects (yet). When you hit the half-dozen mark, then do the
generic extension.
> > What you understand is not what I meant. What I meant to say was that
> > you don't have to use the current "struct Backend" to access the data
> > store. All you really need to do is to provide some vector table so
> > that other people can write methods to access other storage media.
>
> Right, but who calls/fills in the vector table.
a business object would call a vector. When a business-object backend
is initialized, it fills in the vector table.
> Who calls "save"?
gnc-session does.
> And how does the caller know all the various "save" functions to call
> out to?
most of the 'save' functions are known only to the business object GUI.
So, for example, user completes editing a customer name, clicks on the
OK button. The GUI then calls the gncCustomerEditCommit() method.
The gncCustomerEditCommit() method looks at the business backend,
to see if the customer_commit() vector is not null, and if so, calls it.
The customer_commit() vector then scribles to an sql database, or
invokes xml-rpc to some server.
The business backend does have to have one public vector called
gnc_session_is_ending_now_so_finish_up(), so that gnc-session.c can call
it when the user goes to exit gnucash.
So maybe I answered my own question? A generic core-object is a void *
pointer to data, and a vector called gnc_session_is_ending_now_so_finish_up().
If you want to pretty it up with version number and an ascii string
name, I guess that's harmless.
So I can now retract the statements I make above. If you build a real
simple core-object thing, that's OK. But we really need to document
the reason for its existance, so that future coders understand ....
People already complain that gnucash is too complex ...
> With a registration method, you can centralize the 'save()' api but
> have it call out to any number of "save()" methods. At least in
> theory.
Right. Yes. Although save() is Another Evil Unix Thing (TM). Another
symptom of a file-centric view of the world. If unix was
transactional, you wouldn't need a save. Transactional commits are
good. Save is bad.
> > I do beleive that a storage backend for business objects will probably
> > be very tightly integrated with the engine backend. But that does
> > not mean that business object calls have to flow through the current
> > 'struct backend' to acheive this. There's nothing wrong (that I see)
> > with design a different 'struct busns_back' for use by the business
> > objects; I also se a few advantages for keeping it separate. The
> > actual code underneath 'struct busns_back' might be very tightly
> > integrate with the the code underneath 'struct backend', but that
> > is an implementation issue, not an architecture issue.
>
> It seems strange to me to have "coupled code" that uses a different
> interface. Besides, it still doesn't easilly allow extension.
Well, lets look at what the business backend needs to do. Lets assume
the SQL backend. First, in needs to talk to the same SQL server
and the engine, otherwise this discussion is too weird. So the
business object backend needs to ask/be told/peek at whatever the
engine backend is using for its sql connection.
Next, the business backend will presumably have weird routines in it
that I can't guess: not just a 'save()', but also some transactional
routines, like 'commit_changes_made_to_customer()' or maybe
'contact_ldap_server()', or Lord knows what. I can't guess.
What do you want to do? hard code each of these new vectors into
the existing BackendP.h? I suppose you can, but it makes more sense to
me to allow core-object developers to invent thier own backend as they
wish. If they want to recycle the sql connection, they need to steal
that somehow; I don't care how, as long as its not architected.
> If the backends are tightly coupled via implementation, why not
> couple them via API?
Sure, I'm kind of neutral. Just realize, whenever you modify
BackendP.h, you have to go and modify the correesponding code
in PostgresBackend, RpcBackend, FileBackend, HttpBackend, etc.
I couldn't help but notice that certain anonymous individuals
were more than happy to break the http backend because they couldn't be
bothered to propagate the changes to all the different places.
That's the problem with changing API's -- they affect everybody.
> Simiarly, assume a File backend -- how do you store the "two backend"
> data into one file? Or are you presuming that the current
> engine would store data in file 1, and the business backend would
> store into file2?
No, I'm assuming that the engine backend and the business backend would
be implemented in the same hunk of code, in the same
lib-whatever-backend.so and so there would be no confusion over these
kinds of issues.
> I guess my confusion is that if src/backend/* needs to be changed, why
> not just change Backend * at the same time and provide a coherent
> Backend interface instead of multiple interfaces to essentially the
> same code.
Yeah, that's OK too. I don't think I feel strongly on this issue.
> each core object (Account's, Transactions, Splits, Customers,
> Invoices, etc)
Gack. My view of the world is not split like this. I view
accounts/transacations/splits to all be tangled up in one and the same
core object, the 'account heirarchy'. You can't really untangle them.
By contrast, 'prices' really live in thier own little world. So
'prices' could be split into a separate core object.
--linas
--
pub 1024D/01045933 2001-02-01 Linas Vepstas (Labas!) <linas@linas.org>
PGP Key fingerprint = 8305 2521 6000 0B5E 8984 3F54 64A9 9A82 0104 5933