Re: aggregate boundries
- From: "kurbylogic@xxxxxxxxxxx" <kurbylogic@xxxxxxxxxxx>
- Date: 30 Jan 2006 22:49:18 -0800
H. S. Lahman wrote:
> Responding to Kurbylogic...
>
> > I'm attempting to create a program to help me budget my money. I
> > want to do something different form money/quicken in that I assign my
> > existing assets to categories such as mortgage and savings each expense
> > will then be deducted from the category and credited by my paychecks
> > allowing me to more easily determine how much money I have set aside
> > for the category. But I'm having a bit of trouble determining how
> > to maintain the relationship invariants in OO.
> >
> > Account, Category, and Payee are root objects
> > Category is a composite containing other Categories
> > Account owns Transactions and Transaction owns TransactionCategory
>
> OK, so far this sounds like:
>
> * identified in R1
> [Transaction] -------------------------- [Payee]
> | * pays 1
> | identified in
> |
> | R2
> |
> | updates
> | 1 1 child of
> [Account] [Category] -----------+
> | | |
> +--------+--------------+ |
> | R3 | R4
> _ |
> V * contains |
> [CategoryContext] ----------------------+
> + currentBalance
>
> Note that the Category structure is just a simple GoF Composite pattern.
> What I am unclear about is what [Payee] represents. I assume this is
> who the money goes to when a check is written. You suggest below that
> [Payee] may be hierarchically organized as well. In that case one would
> need a similar Composite pattern for [Payee] as well (i.e., the name on
> the check is only provided by a leaf).
>
my ascii uml isn't very good but more like :
(when I preview my the size of my spaces seem to change and the lines
go everywhere, but if you'll note that transaction category is a many
many relationship with the association class transactioncategory
holding
the amountassigned you might be able to make sence of it)
summarizes <composite>
[Transaction] * ------- assigned to * [Category]
| * | * | *|
| | pays 1 [TransactionCategory] |
| [Payee] +amountAssigned |
| | * |
| | |
| updates | tracked by |
[Account] * ---+ -------------1 [DataContext] 1 +
Payee has a user friendly name (as store numbers and localities are
often appended in ofx files) and provides name matching and automatic
category assignment services. payee.IsMatch("Barnes & Nobel -
Highlands Ranch"), payee.AssignCategories(transaction) adds a
TransacitonCategory to Books Category with full transaciton amount.
Employeer (a payer rather then payee perhaps but still goes into the
"payee" field on transaction) however knows my net pay and how to find
the find the budget for that period, the budget will then determine
what portion of the transaciton amount should be assigned to which
categories (ie if I budgeted 100$ for books but only spent 50$ on books
then assign 50$ to books not 100$). Any amount left over can be
assigned to extra loan payments, savings and/or investments in whatever
way the budget decides is best. Lenders could perhaps provide loan
amount and amoritization statement for example.
A transaction can only have one payee thus in my database transaction
has a foreign key to payee. The transaction needs to know the identity
of the payee to save itself, payees will be saved prior to transactions
and the identity property updated However it does seem that payee
needs to know a lot more about transactions (dates, names, amounts)
then transaction needs to know about payee (the name and identity).
Transaction really doesn't even need to know the payee name because the
caller knows about payees and could ask the payee but would still need
to determine which payee to ask, I suppose it could ask all of them if
they contain the given transaction but I think this will create more
problems then it solves. The only thing it seems transaction really
does need to know is the identity. The name of the payee can be
changed, I might be able to cascade the changes locally and modify the
database gatway to accept the name rather then ID however if the name
of the payee is changed on a different machine the service would not be
able to find the payee by name when the changes are saved.
The problem I think that I'm having is that in OO the object reference
is the identity thus adding unnecessary dependancies and enables the
holder of the idenity/object reference to send messages to the object
directly without going throught the proper channels, whereas in the
database world you might have a the transactionId but you cannot ask
the transaction table to update the transaction with a payeeId it does
not know how to verify instead you must ask the database which knows
about foriegn key constraints, the database asks the payee table to
validate the payeeId and only then sends a message to the transaction
table to update transaction x with the given payeeid. This indirection
is missing and in my model where updates must go through the
datacontext, but since the identity is also the object reference, once
that identity is given to the client the client can "misuse" that
identity by sending a message directly to the object without going
through the datacontext which knows how to validate the invariants
before setting updating the object with the given identity.
If I wrap the object the client would only be able to the properties
defiend on the wrapper.
The best idea I can come up with is to create an object that acts as a
suragate identity in place of the database identity, thus my
datacontext looks just like my web service interface but uses these
surragate objects for identity instead.
Thus:
public class PayeeId {}
public class AccountId {}
....
public class Account {
AccountId AccountId { get; set; }
public string Name { get; set; }
}
Account account = dataContext.GetAccountByName("Checking");
transaction = new Transaction(....);
datacontext.Add(account.AccountId, transaction)
/* implemented something like:
{
if(!accounts.exists(accountid)) throw exception
if(t.payeeid != null && !payees.exists(t.payeeid) throw exception;
// other verifications
//update aco*** and category balances
// store transaction list seperate from account detial
IList accountTransactions= _transactions[accountid]
transaction.TransactionId = new TransactionId(); // assign identity
accountTransactions.addtransaction(ttransaction.Clone()) // clone
object
}
*/
I think this could work because now I would have something other than
object reference to use as identity thus allowing me to clone the
object and protect it from direct modifications by the client, yet
without datacontext needing to know the various payee subtypes.
Thoughts?
> > The invariants are that Transaction.Payee is member of Payees and
> > TransactionCategory.Category is member of Categories
>
> I'm not sure why Transaction needs to know the Category. Isn't a
> Transaction just an update of a specific Account? If so, then the
> Category is defined by the R4 hierarchical relationship. So if one has
> a Transaction in hand one just needs to navigate R2 -> R4 to obtain the
> immediate Category. One can then "walk" all the way up the Category
> hierarchy via R4 to handle the debits and credits at each level.
>
> > I've created a larger aggregate DataContext to encapsulate the list
> > of accounts, payees and root categories. I use an identity map during
>
> I suspect that Accounts and Payees are leaves in orthogonal data
> structures. So I would probably not have as wide an umbrella as you
> suggest here for DataContext.
>
> > load to ensure the correct instances are assigned to each object. The
> > model is not bi-directional so if I ask for all transactions by
> > category I search every transactioncategory of every transaction in
> > every account. My primary view will be by category so I think a
> > bi-directional relation here might be best but at the moment category
> > knows nothing about transaction, and I've optimized the search a bit
> > by assigning left/right tree numbers to each category. The question I
> > have is what the best way to control modifications is. My first
> > thought was that when context is saved my thinking was I would
> > enumerate all transactions in the datacontext if no id is assigned its
> > new, if transaction.HasChanges I update and if the transaction was
> > loaded but is no longer in the transaction collection it was deleted,
> > and likewise for each payee, category, and transaction.
> >
> > The problem is that this doesn't ensure the invariants above are not
> > violated, context.getAccounts()[0].getTransactions()[0].Payee = new
> > Payee("Test"); and similarly in relation to categories. I don't
> > want to reload the data on every change and in some cases is not
> > possible as I'm also performing updates via an Indigo service to
> > allow me to access my info from home or work but at some clients I'm
> > not allowed to connect my laptop to the network and so I wanted to be
> > able to save a serialized copy of the datacontext to disk and queue
> > modifications so that they can be sent later when connection is
> > available.
>
> This is a very different and potentially very nasty problem.
> Essentially your application is caching data from the DB than is not in
> synch with the DB for extended periods of time. Money/Quicken don't
> have this problem because they update the DB with every transaction
> immediately. (Note that newer versions of Quicken don't even have a
> Save button anymore.) Enter stage left, tripping: DB replication.
>
> You have an added problem due to the complexity (nesting) of your
> summaries. Money/Quicken basically have only one total number to keep
> track of at the Account level, the current balance. Your summaries add
> another level of complexity due to the nesting and the requirement that
> the subcatagories sum to the supercategory.
>
> However, I don't think the problem is insurmountable if you have a
> complete copy of the DB on your laptop. [If you don't then you will
> have a problem reading Transactions for UI display when the base is
> unavailable. B-)] So long as that laptop copy is internally consistent
> for recent activity, you can just do the brute force approach and copy
> it to you home desktop or whatever when you get the chance. IOW, do a
> poor man's replication by treating the home server as a backup.
All categories and payees will be copied to the client but only
transactions for the current period. The reason I don't want to save
changes immediately is I want to be able to see to preview the changes
that will be made when I import an ofx file, if I discover the rules
(auto categorization) that I've defined are not behaving correctly I
can change them without needing to manually fix them afterwards. I
just like to make things as complicated as possible :) but really I'm
actually trying to force myself to tackle some issues I've some how
managed to otherwise avoid.
>
> For the posting mechanics to ensure internal consistency you just need
> to "walk" the Composite tree above and make the same adjustment at every
> level above Account _when you update Account_. That will ensure that
> the numbers are consistent so long as you do the update prior to
> processing any other change. [Note that if the transaction in hand is
> changed at the account level you obviously have to post the delta of the
> change to Account and the categories. But that delta will be the same
> at every level.]
>
> To do that you just need to keep a lastBalance attribute in Transaction.
> When you create a Transaction or have completed the update of Account
> and the Categories for a change, you set lastBalance to currentBalance.
> So the only time they will be different is when you have made a change
> to an existing transaction in the UI. You just need to look for that
> when you post to Account to determine whether you need the straight
> amount or a delta. IOW, just make sure you complete the posting before
> processing another Return in the UI.
This is what I want to do but unfortunatly the client could perhpas
bypass the datacontext by adding the transaction directly to the
account without updating the effected categories so I tryied to avoid
the issue by storing the data in only one place (borrowing from the
relational model) unfortunatly this doesn't work in OO because even if
the data is stored only in one place I can't enforce the uniqueness
constraints if the client bypasses the datacontext.
>
>
> *************
> There is nothing wrong with me that could
> not be cured by a capful of Drano.
>
> H. S. Lahman
> hsl@xxxxxxxxxxxxxxxxx
> Pathfinder Solutions -- Put MDA to Work
> http://www.pathfindermda.com
> blog: http://pathfinderpeople.blogs.com/hslahman
> (888)OOA-PATH
.
- Follow-Ups:
- Re: aggregate boundries
- From: H. S. Lahman
- Re: aggregate boundries
- References:
- aggregate boundries
- From: kurbylogic@xxxxxxxxxxx
- Re: aggregate boundries
- From: H. S. Lahman
- aggregate boundries
- Prev by Date: Re: SQL
- Next by Date: Re: File system and UML diagrams
- Previous by thread: Re: aggregate boundries
- Next by thread: Re: aggregate boundries
- Index(es):