[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