ok, so now I am at a point where my basic code generator for table maintenance is nearly there. Given a table, it spits out the crud, valueobject and test unit classes. More importantly, it also preserves custom user changes in any previous version of the crud and valueobject.
I was just thinking of making the custom code a little easier and it crossed my mind to change the layout slightly.
Currently I have
/ValueObject/classname.cls
/lib/crud/classname.cls
/test/unittest/unit/classname.cls
the get / save / remove methods in the valueobject are placeholders for the code sitting in the crud class
What I was thinking of was
/ValueObject/classname.cls
/lib/crud/classname.cls
/lib/classname.cls
/test/unittest/unit/classname.cls
where /lib/classname.cls inherits the abstract /lib/crud/classname.cls. This /lib/classname.cls would only be created if the file doesn't already exist, and simply be a new class to inherit the /lib/crud/classname.cls. The get / save / remove methods in the valueobject are then placeholders for the code sitting in the /lib/classname.cls class
People can then add their own code to the /lib/classname.cls file without any loss when the classes are regenerated.
Am I on the right track here ?
jmls wrote:
ok, so now I am at a point where my basic code generator for table maintenance is nearly there. Given a table, it spits out the crud, valueobject and test unit classes. More importantly, it also preserves custom user changes in any previous version of the crud and valueobject.
I was just thinking of making the custom code a little easier and it crossed my mind to change the layout slightly.
Currently I have
/ValueObject/classname.cls
/lib/crud/classname.cls
/test/unittest/unit/classname.cls
the get / save / remove methods in the valueobject are placeholders for the code sitting in the crud class
What I was thinking of was
/ValueObject/classname.cls
/lib/crud/classname.cls
/lib/classname.cls
/test/unittest/unit/classname.cls
where /lib/classname.cls inherits the abstract /lib/crud/classname.cls. This /lib/classname.cls would only be created if the file doesn't already exist, and simply be a new class to inherit the /lib/crud/classname.cls. The get / save / remove methods in the valueobject are then placeholders for the code sitting in the /lib/classname.cls class
People can then add their own code to the /lib/classname.cls file without any loss when the classes are regenerated.
Am I on the right track here ?
I think so - I like the use of abstract methods for enforcing/ensuring the existence of methods in a derived class.
Relying on comments or annotations in the code as markers for code-here-or-not-here seems a little flaky to me (even though almost everyone uses it, including OEA for InitializeComponent) since there's no guarantee that some Code Dinosaur will blithley remove the "stuff I don't see the point of and that is cluttering up my code, man" and then you're up a certain creek without a paddle.
-- peter
so, if you wanted to extend the valueobject (custom properties etc),
would you create all of those as abstracts, and have another layer
above that inheriting the base value object ? (much like the thinking
behind extending the lib / crud stuff I was talking about).
also, how would you extend the before/after create/update/read/delete
parts of the crud library ? (for example , a calculated field, or
extra code to run before / after delete ) - create an override method,
do the calculation and then call the standard method ? Put a callback
in the crud method ? Raise an event ?
So many questions. So little time
Not sure about your architecture here, but I would *never* touch any of the generated code.
Instead always create a class that inherits from the generated code where you do your custom coding.
That way you can override whatever you want. The only code you ever have to look at (= maintain) is the custom class. The standard code can be regenerated at any time.
Even though we are not using a generator, we are always doing it this way in our generic environment. The factory checks if there is a custom class (based on a naming convention) and starts that if it exists, otherwise it starts the generic class (in your case: the generated class).
Works like a charm and we only see custom code. That IMHO is the only code we care about.
-peter
When I see something like this, my first reaction is to think in terms of the trouble one gets into from trying to do something without using the right tool. By right tool I mean model to code translation ... no naming conventions to goof up, no markers to mispell, architecture configurable to need, etc. Unfortunately, I don't have a working ABL MDA tool to hand you today. You might, however, want to take a look at Phil Magnay's reverse engineering tool and consider what you could cobble together with EA generation of the shells, customization in OEA, and reverse engineering to move the custom code back into the model where it will get included in the next generation. There is a lot suboptimum about this (look for a whitepaper soon), but it might give you a better foundation than trying to cobble something together on your own.
Absent that, I don't know exactly how you are doing your generation today, but I might consider something like my old Specification-Driven Development tool from the early 90s (no whitepaper, sorry). This used a general purpose macro processor, templates, and specification files. The templates had a lot of variations which could be selected by a simple note in the specification file. This kept one from writing the same variations by hand over and over again. Whenever I recognized that I was going to use a particular pattern more than a couple of times, I would add it to the options available in the template. The templates also had hooks for genuinely custom code which one put in the specification file. The result was the ability to regenerate at any time and yet to continue to evolve the templates so that, for example, a new feature could be added and one could simply run the generator on everything and have the new feature included everywhere.
Julian said :
also, how would you extend the before/after create/update/read/delete
parts of the crud library ? (for example , a calculated field, or
extra code to run before / after delete ) - create an override method,
do the calculation and then call the standard method ? Put a callback
in the crud method ? Raise an event ?
This I'm not 100% about - still working through it myself, and I think it depends where you are in the stack, and how many other objects need to react (events vs. non-events), and what your design is (sorry :). If you have a general library that does most of the work, I'd think a callback would make sense for specialized behavior (ie don't customize the library). If there are multiple consumers, events make sense. My inclination is to override, since that's easier to read/understand, and then only the custom code appears in the derived object; but I do really think it depends.
-- peter
Oh, it occurs to me that I do have a slide deck on SDD. It is more a marketing piece, but I would gladly send it to you if you were interested.
Oh, it occurs to me that I do have a slide deck on SDD. It is more a marketing piece, but I would gladly send it to you if you were interested.
How about attaching it here?
It *is* 1992 era technology and, moreover, is rather more a marketing than a technical presentation, so I don't know that I would put it out there generally. E.g., it isn't on my website and I don't even have a page on the website talking about SDD since it is yesterday's (and then some) technology. But, I am happy to send it to individuals and talk about the ideas. I would much rather point people to model to code translation, aka MDA, and will have some whitepapers on that soon.