[Glass] GemStone transactions & Magritte
Iwan Vosloo via Glass
glass at lists.gemtalksystems.com
Tue Sep 8 05:38:20 PDT 2015
Hello,
We have some hacks and plasters on top of standard
Seaside(3.1.4)/Magritte(3.3.1), which we use in development on Pharo and
in production on GemStone. I think it is hurting us to be so far removed
from what everyone else uses and talks about out there. I also think we
could add value to the community if, instead of making hasty hacks to
get something to work, we provided some feedback about our needs and
also contribute some of what we have done. Ever present pressure
permitting, of course...
This longish post and associated code is an attempt in that direction.
Please let us know what you think.
The code is at github[1] . I am specifically talking about the packages:
- UserErrorSpike-Core
- UserErrorSpike-Pharo (not everything will work in Pharo though...)
- UserErrorSpike-Tests
One of the ways in which I think GemStone can add value to
Seaside/Magritte is GemStone's ability to roll back a transaction.
Our fundamental use case goes as follows:
We have a complex domain, with a lot of behaviour implemented on complex
domain objects.
We have a lot of validations written for Magritte using addCondition: on
MAElementDescriptions or MAContainers.
For example:
We have a screen where you can capture an investment you want to make.
On it, you specify the amount you want to invest, and you pick several
funds you want to invest into, specifying how much of your investment
amount must go where. A simplified example ([] denotes an input field,
<> denotes a button):
Investment amount: [ 20000 ]
Funds:
+-------+------------+--------+--------+
| Fund | Percentage | Amount | |
+-------+------------+--------+--------+
| A | [10 %] | 2000 | _remove_ |
| B | [50 %] | 10000 | _remove_ |
+-------+------------+--------+--------+
<add fund>
We have to validate simple things like:
- You are only allowed to type a number into Investment amount, and the
Percentage column for each fund.
And more complex things like:
- All the percentages you typed add up to 100%
And even more complex things like:
- How much you are allowed to invest into which kinds of funds as
dictated by local laws.
This screen is built on top of a little tree of domain objects, and each
of them contains logic to do various related calculations.
If you do all of the validations using Magritte conditions, you have a
problem: Inside a condition block, you only have the memento, and you
cannot call methods on the actual domain object instance involved. Yet,
the things we want to validate are quite complicated to calculate, and
we really need to be able to implement such calculation on our domain
objects themselves.
We thought that complex validations would work better if we can execute
methods on the underlying domain objects to check them. For this to
work, one would need to validate simple user input (ie, did you type
only numbers), write the (now valid ito user input) mementos to the
domain and THEN have a second step that "validates" the domain according
to its own logic.
The trouble with this is that at this point you have now already
modified the domain... and this will be (database) committed to GemStone
by default. We would have liked to commit the mementos to the domain, do
validations on the domain, and then roll back the GemStone database
transaction if the second validation step fails.
At this point we also realised that "valid" means different things to
the domain, depending on what you want to do with the domain. As a
result, the second step of "domain"validation can just happen inside
domain code during a normal action callback linked to a Button, say. For
example: InvestmentInstruction>>submit can check that all its parts add
up to 100% and are compliant with laws. And if such an action inside the
domain picks up a problem, it can just raise an exception.
The job then would be to catch this exception, abort just the
side-effects of having done that action, redirect the user to stay on
the same page/component, but also display the error message like you
would a validation error message.
This way we can also test domain-level validations as part of
domain-only tests, without the need for dealing with UI overheads.
The example code here[1] may very likely not be the nicest way to
accomplish this... but that is what I could come up with at present.
Here's what it does:
1) UserError is the exception you want to raise in the domain.
2) TransactionBoundaryFilter should be added (first) to your application
to ensure seaside state is committed before handling the rest of the
request.
(This allows one to abort deeper in the stack.)
3) AbortableAction is then a wrapper around the block one passes to
addValidatedForm: (for example), which takes care of catching any
UserErrors and dealing appropriately with transactions in response. It
does a little more though: it always validates (as in Magritte condition
validate), and then writes mementos to the domain BEFORE the wrapped
block is executed.
4) UserErrorExampleApp shows this in action
[1] https://github.com/finworks/contrib
Regards
- Iwan
--
Reahl, the Python only web framework:http://www.reahl.org
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.gemtalksystems.com/mailman/private/glass/attachments/20150908/f64fb95f/attachment.html>
More information about the Glass
mailing list