Sunday, September 25, 2022

That's all Folks!

And this is as much as I have to say about model driven development for now.

I hope this short intro can be helpful to a beginner in this topic or to show someone interested in it what options this approach has to offer.

Because model driven development is first of all about the approach, not about the tooling, it is about way of thinking about the problem - in models first.

It forces you to structure your thinking.

It exposes this thinking to other people.

It forces you to define ubiquitous language (DDD, anyone?).

It creates a foundation for basically any discussion with other people.

It creates enforceable documentation.

And this list could go on and on. I could easily write 5-10 more points, but I think you get the point.

Yes, model driven development is not magic.

Yes, it is not a silver bullet.

But in my 20 years of professional software engineering experience, this is as close to a silver bullet as it gets.

Thursday, September 10, 2015

Pragmatic MDD: Workflows and more

So the last time we were facing 2 problems: A lot of code in a file for just one transformation (and part of this code could be potentially reusable later) and definition of workflows.

Let's begin with the solution for the first problem. It's an easy one.

We proceed here exactly like before: We define a separate transformation for the setting of meaningful defaults. Since there is not model-to-text transformation, result of the runner is just another model, that gets saved in the output folder.

This changes things slightly - Since the input model of the PojoWithAutoupdate transformation is not our original model anymore (remember, we just moved all the default settings to a separate transformation), but the output of the previous step (the new default setting transformation), we also need to change the setup of PojoWithAutoupdate a bit: From this to this, the model name and model path have changed.

So now we have 2 transformations and somehow they need to be executed one after another.. How can we do that?

But before we answer this question, let's solve one more problem. Since Java wants to have package names consistent with the folder structure where files are, at the very beginning we have to setup all the missing folders and, before that, remove all the old files still remaining there. This is done in one more workflow step. This is for sure not the most elegant way, but it does its job.

So now we have even 3 workflow steps that need to be triggered one after another. And this is exactly what we do here: Just triggering one step after another, passing all the overrides as parameters the same way we did before over the command line.

Tuesday, July 21, 2015

Pragmatic MDD: Hibernate injection

Ok, now we have a domain model - perhaps not the most exciting one, but it has everything we usually need: inheritance, custom data types, an enum.

So let's continue and go a little bit deeper: How about adding JPA hibernate annotations to our classes? Of course, we don't have to use hibernate, but it really doesn't matter, which ORM we take, the principle is always the same.

How can we add these annotations? The same way we have customized our setup before: By declaring injection points in the primary transformations, overriding these points with our custom logic in secondary transformation and then load this secondary transformation on workflow execution.

Enough theory, how does the code really looks like?

1) The normal POJO m2t transformation - here we added some empty methods, like preClassDeclaration and preAssociationFieldDeclaration

2) Override of these empty methods in hibernate injector - here we add our annotations

3) Loading in the runner happens exactly the same way as before - we don't need to change anything

This solution is ok, but now we have several new problems.

First of all, we have a lot of code to set meaningful defaults for multiplicity, naming and aggregation. This is ok as long as we have only one transformation, but what if we have more? This code should be in its own module, since such logic is not really related to the POJO generation.

And this creates a second problem - if we have 2 different transformations, adding of the default values and POJO generation, how can we trigger them one after another? For sure not manually.

Let's see how to define workflows next time.

Wednesday, July 1, 2015

Pragmatic MDD: Can we scale?

So till now our model looked really very limited: just 2 classes, no enums, no custom types..

Let's change it.

If you take a look at the original model, there are several things we need to consider:

- AccountState enum in Account class

- RFID data type for tag attribute in BookItem class

- And let's just for fun add one more custom type: Book.ISBN will become a String with length 13

And this is how we will model it in JSON: nothing surprising, just an additional list of custom types.

So how shall we implement it?

First, we extend the UML transformations to see some nice pictures: 3 more lines of code to copy the enums and we are done.

Now let's proceed to the object transformations. Not a big change here too:

- m2m: resolving of custom types and enums

- m2t: additional template for enums

- runner: every enum needs to be written in its own file

And this is already all, now we have a fully working model!

Tuesday, June 16, 2015

Pragmatic MDD: I miss UML

Let's take a look at our JSON model. It looks kind of like a .... JSON model.

You can't really show it to anyone. You can't discuss it with other people. You can't brag with it in front of your boss. You can't put "UML" on your CV. You basically can't do anything with it. Except of generating code out of it, of course.

But wouldn't it be nice to have an UML model as a basis of any design discussion?

Yes, it would.

I don't think that UML should be a starting point, the source of our pragmatic MDD approach, but as one of many targets, as a result of one of the many different transformations we are going to write, it has for sure its merits.

So how are we going to do it, how will we generate UML out of our JSON model?

Will we write some kind of visual UML editor? Isn't it a completely stupid idea, since there are already so many of them out there?

Yes, indeed, it is. The net is vast and infinite, it has already everything we need to solve our problem.

So let's use this very nice plantuml tool to produce some UML diagrams. The only thing we need to do is to transform the JSON model into the plantuml format.

Here is the code how to do it. It's super easy. Seriously, it couldn't be easier.

Perhaps you noticed, that we are missing the runner module here. This is because of the way we are going to use plantuml.

Here is a simple html website with a fake image tag. In the source of this image tag we are going to put the uml text (result of the transformation) and then we will let plantuml do its magic.

But I would like to mention 2 things:

1) Since we are using an html website, let's switch to JavaScript here. We could perfectly well also continue with CoffeeScript using this example, but it actually complicates things way more than necessary. But in that case, of course, to embed the code in html website, we need to compile our CoffeeScript transformations to JavaScript, which is super simple to do: run coffee -b -c filename.coffee and you are done.

2) Run it in Firefox. Chrome and IE don't work out of the box, but why bother anyway.

Download the code, open the website and what do you see?

How much easier can it get?

Tuesday, June 9, 2015

Pragmatic MDD: Divide and conquer

So last time we found some problems with our code:

- Everything was in one file and it got quite big

- There was no way to customize code generation – e.g. to set the folder where the generated files should be saved

- We didn't have any tests at all (and that is fine for a throw away code, but a must in case we want to maintain it).

Let's see how we can improve the situation step by step.

File is too big? Let's split it.

Before we saw, that our whole process consists of 2 separate steps - model-to-model transformation (m2m) and model-to-text transformation (m2t). So perhaps we should just split these 2 parts into separate files? But why stop there, why not split it into 3 files: One with all the function definitions required for m2m, one with everything for m2t and one that will actually call our m2m and m2t modules, be responsible for the communication with file system and do whatever else is needed. Let's call this third module "runner".

So this is how it will look like. Is not too bad, I think. Such splitting has also one more advantage - testing of the transformations gets incredibly easy. Since all the functions in m2m and m2t modules are side effect free, we can test all of them separately without creating any mocks or writing some complicated setup. Take a look, how the test modules are written.

For the asserts, we are using chai library, as test framework mocha and tests can be executed locally with following command: mocha . --recursive --compilers coffee:coffee-script/register

So far we fixed 2 problems: File sizing, complexity related to it and testing. But what about the customization?

Let's define a separate setup module containing methods, that we will always need, like input folder, where our model is, output folder, where the generated files should be saved and model name (name of the file with the model). Actually, we don't need to define any functions in this module at all - thanks to the dynamic nature of JavaScript we could just add new methods and override old ones on the fly, but still, since we can be quite sure, that these 3 methods will always be needed, let's define them in this module explicitly (even when their bodies will remain empty). Also notice, that we don't define anything regarding namespace here - since namespace will be needed only for such specific transformations as to POJOs, we shouldn't put it in this common setup file.

Now we should add some implementation to empty methods needed for our case. And this is exactly what we do in custom setup file - we are loading our default setup file with empty method bodies, override the old methods and add a new one to provide the namespace for our POJOs.

So far so good.

But how can we access this custom setup file from the runner, where all this information about folders is needed, without referencing it directly?

Via command line.

Take a look at the lines 4 and 5 in the runner. Here first we are loading the default implementation of the setup file with its empty implementations, but then we also load all the modules, that were passed to us via command line.

So if we execute something like coffee runner.coffee ..\..\UseCases\setup.coffee, the application specific setup file will be loaded after the empty setup file, in this way overriding it's old methods and adding new ones. No need for any direct dependencies here.

Monday, June 1, 2015

Pragmatic MDD: Show me some code

Finally! Finally we can take a look at some code!

As discussed last time, we start with generating POJOs out of the model, the simplest exercise ever, and this is exactly what our implementation is doing (BTW, to run the code, just type coffee pojo.coffee).

But perhaps first I should make a remark concerning CoffeeScript syntax: If you are a JavaScript developer and don't know this syntax, here is a nice comparison of CoffeeScript and ES6 (and you might also notice, that ES6 will already include support for most of the features we are using). If you are coming from Java, I hope you are familiar with Stream API. If you are a .NET developer, notice similarities with LINQ. Otherwise you can always take a look at this tiny overview of the language features on the CoffeeScript homepage.

So let's come back to the code now.

It's so small, I don't think we need to spend too much time discussing the details, but the general structure is probably worth a look.

As we have seen from the theoretical part last time, our code needs to contain 2 parts: Model-to-model transformation(m2m) and model-to-text-transformation(m2t).

This separation is also clearly seen from the code:

- m2m: We transform the inputModel (our original JSON model) into the outputModel in such a way, so that it contains all the necessary information for our templates to generate code out of it. And this is exactly what we are doing in lines 9-22: Copy everything except the associations from the original model and then process the associations to include information about them in the corresponding class definitions (don’t forget to add default names for association ends: since in our model the name “wrote” is defined only on one end, for the other one we need to generate something meaningful, like “getAuthors”).

- m2t: We define code generation templates in form of functions (lines 25-43) and then apply these templates to every class of our transformed model (lines 45-49).

Ok, this was simple enough. Altogether less than 50 lines of code, not too bad (even if it is probably not the most meaningful metric in this case).

But let's see how well does this approach scale: how about making our associations auto-updateable?

Here is the code.

So what has changed?

The m2m part got a little bit bigger - now every association belonging to a class not only has to know about the class it references, but also about the class it belongs to. Of course, we could just find all this information on the fly from the model, but why bother, when we can spare this effort by just coping the needed parts. At the end of the day, this is why we use transformations at all.

And what about the m2t part? Here we got a lot more templates: separate templates for attributes and associations, initialization of empty lists..

And, of course, all these different cases in handling of the references: Many to many, one to many, one to one. Setting of an item, resetting of an item. Adding an item, removing an item. It is a lot of templating here.

Unfortunately I don't think we can do much about it - we want to generate code and need to specify templates. This way or another, it needs to be done.

But there are several other problems here:

- Our file got quite big, about 100 lines. This is not so good, it's better to split it.

- What about the package name for our classes? Right now it is taken from the model, but what if we have several models (or actually the same model, but just splitted into packages between different files). Do we then need to put it in every file? Or do we define one "main" file, containing such information? Not good. We need to make it more flexible, we need to pass the package name to our code in some other way. Same goes for the input folder, where the model can be found, and output folder, where we want to have our generated code saved.

- And what about the testing? Right now, with all this file reading and writing, it would be impossible to write unit tests for such a module. But we need to write tests. We are pragmatic here, but not stupid.

So let's see what we can do about these issues next time.