Questions about modular applications with Griffon

classic Classic list List threaded Threaded
14 messages Options
Reply | Threaded
Open this post in threaded view
|

Questions about modular applications with Griffon

Marcelo
I would like to know how to decouple to the extreme a modular application. For this task I took the simplified application from Griffon's tuturial to play with it.

Big thank you! to Andres, who helped me to understand the basics (for the curious, the link of the thread in Stack Overflow is here).

For a little recap, the application so far is composed of 3 modules:
* Launcher
* View (contains MVC)
* Service

What I had trouble with is with the compile dependency the Launcher module had on the View module.
Reading the Griffon Guide I continued changing the example and created a Griffon Module in the view module that registers an Addon that defines the startup groups. With this modification, the need for the Config file in the Launcher module to actually know which classes of the View module where involved in the MVC group is gone, and its role is reduced to set the name of the application and the autoshutdown property. This also allowed me to reduce the compile dependency to a runtime one.

Why I would ideally want to achieve is to actually break that runtime dependency also. After all, the improved View module is defining the startup groups and the Griffon container knows about it. If this is possible,  the application could be easily extended by adding new modules that know exactly what to do. Is this possible at all?

Another question I have is do I create an application that has multiple windows (lets suppose the user has multiple screens and wants to visualize data on both of them). For this I copied the View module and renamed it to View2, renamed all the classes and provided the new runtime depencency to the Launcher module (here is where I thought it should be a better way...). After that, I registered the Stages (JavaFX here) with the WindowsManager via the attach method with a different names, rename each window title and the classes in this new module to avoid name collisions. The example works but only one window is shown: if I remove the dependency in Launcher to View2, then the original view is shown as expected and vice-versa. If I have both dependencies, only View2 is shown. My suspicion is that the createApplicationContainer of the Griffon Application has something to do with it. Any ideas of how to show both views at the same time?

My goal is to keep working on this example application to provide multiple views and services strictly using the Griffon Framework only, and once it looks decent propose it to be included in the Griffon Tutorial to have another example of multi module application. Sorry for all the questions (more to come for sure).

Thanks!



 

Reply | Threaded
Open this post in threaded view
|

Re: Questions about modular applications with Griffon

aalmiray
Administrator
Hi Marcelo,

Addons are the way to go in order to define new MVC contributions. You may place the addon anywhere you want. The ideal place in your current setup would be inside the View module, not the Launcher module.

Regarding multiple windows. Griffon will automatically instantiate and configure any MVC groups registered in the `startupGroups` property found in the application's configuration. Usually there's only one group, the default one. If you want both windows to appear one after another during startup time then add the name of the second MVC group to that property.

Normally what one does is create an instance of the second MVC group at a certain step. Say for example the View of mvc1 has a button. When you click this button an action reacts to that event, the action belongs to controller1. This action is responsible for creating an instance of mvc2.

All this being said, based on your previous message it looks to me that what you want to achieve is option #1.

Cheers,
Andres
Reply | Threaded
Open this post in threaded view
|

Re: Questions about modular applications with Griffon

Marcelo
This post was updated on .
Hi Andres,

Thanks you for your answer. I am still a bit confused here.

If each addon has the responsibility to define the MVC groups and also overrides the getStartupGroups() method, shouldn't Griffon respect that and add the groups instead of erasing what other addons already defined? Isn't this a bug?

In my view the framework is asking the addons for the groups to instantiate at startup (it does call each defined getStartupGroups() method)  and then it decides what to actually do. Why only one module should define *all* startups groups? Doesn't the current behavior defeat the 'pull' behavior of the design and increases coupling by requiring to modify a configuration instead of declaring what they want to happen? If the current behavior is by design, wouldn't be preferred to get rid of getStartupGroups() altogether and require addons to modify the configuration directly by overriding the public void init() method?

Also, trying to investigate this issue before posting here I registered Initialize, Startup and Ready hooks in my Launcher module to print the artifacts and application configuration, and I could not see anything indicating the existence of a property startupGroups:

INITIALIZE
** Artifacts
[]

** Application Configuration
application = {title=sample, autoShutdown=true}
application.title = sample
application.autoShutdown = true
[2016-08-24 13:21:02,921] [griffon-pool-1-thread-1] INFO  griffon.javafx.JavaFXGriffonApplication - Initializing all sample2ViewAddon startup groups: [sample2]

STARTUP
** Artifacts
[Artifact[view] > Sample, Artifact[view] > Sample2, Artifact[controller] > Sample, Artifact[controller] > Sample2, Artifact[service] > Sample, Artifact[model] > Sample, Artifact[model] > Sample2]

** Application Configuration
application = {title=sample, autoShutdown=true}
application.title = sample
application.autoShutdown = true

READY
** Artifacts
[Artifact[view] > Sample, Artifact[view] > Sample2, Artifact[controller] > Sample, Artifact[controller] > Sample2, Artifact[service] > Sample, Artifact[model] > Sample, Artifact[model] > Sample2]

** Application Configuration
application = {title=sample, autoShutdown=true}
application.title = sample
application.autoShutdown = true


What I could see is that after the Initialize hook was called, Griffon decided to instantiate the startup groups of the second view module of my application.

As an alternative, can I manually instantiate groups from the Startup hook if none of my addons define the startup groups? How can I do that? I know Griffon would throw an ArrayIndexOutOfBoundsException if no startup groups are defined:

java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 0
        at griffon.javafx.AbstractJavaFXGriffonApplication.showStartingWindow(AbstractJavaFXGriffonApplication.java:323)
        at griffon.javafx.AbstractJavaFXGriffonApplication.ready(AbstractJavaFXGriffonApplication.java:311)
        at griffon.javafx.JavaFXGriffonApplication$1.run(JavaFXGriffonApplication.java:93)

Thanks!
Reply | Threaded
Open this post in threaded view
|

Re: Questions about modular applications with Griffon

aalmiray
Administrator
Hi Marcelo,

I'm confused too ;-) The Griffon runtime does not override the values for startupGroups. Startup groups can be defined in two places
- the application's configuration
- inside an addon implementaiton

The application first reads the value of `application.startupGroups` (a property (if found) in the application's configuration). Next it reads the startupGroups property defined by each addon.

https://github.com/griffon/griffon/blob/development/subprojects/griffon-core/src/main/java/org/codehaus/griffon/runtime/core/AbstractGriffonApplication.java#L356-L401

Configuration is designed to be immutable. Although there is a subtype named MutableConfiguration that enables developers to change configuration at runtime.

Lastly, the AIOOBE appears to be thrown by https://github.com/griffon/griffon/blob/development/subprojects/griffon-core/src/main/java/org/codehaus/griffon/runtime/core/view/AbstractWindowManager.java#L146 this means there are no windows registered with the WindowManager by the time `getStartupWindow()` is called.

Cheers,
Andres
Reply | Threaded
Open this post in threaded view
|

Re: Questions about modular applications with Griffon

Marcelo
This post was updated on .
Hi Andres,

Sorry it took me a while to get back to this. Anyway, I am making progress with this, but I find I am using hacks in order to do it, by defining a bogus windowsManager.startingWindow property with the value "**none**".

Shouldn't WindowsManager return null as the window object if no starting window is defined in the configuration and no regular windows are registered instead of throwing an exception? I'm asking because the application calling code deals with the possibility of a null window being returned...

Regarding the application configuration, you mentioned MutableConfiguration. Is this the class an application should use to persist the configuration at shutdown for example? Let's say I want the application to remember the position of the opened windows on the screen. I tried the javadocs but there is no much there...

Thanks,

Marcelo.

Reply | Threaded
Open this post in threaded view
|

Re: Questions about modular applications with Griffon

aalmiray
Administrator
Hi Marcelo,

Why do you need to define multiple bogus values for `WindowManager.startingWindow`?

IIRC WindowManager follows these steps to figure out how the starting window:
- if a `startingwindow` parameter is found in the application's Configuration then WM will attempt to locate a matching window by name.
- if no match is found then WM will attempt to display the first window it finds in its list of registered windows.
- an exception is thrown if none of the previous conditions were met.

WM expects at least one View registering a window. It could be the application's main View, or it could be an MVC group provided by an addon, it doesn't matter. If no window is registered with WM then we're going to have a problem.

`MutableConfiguration` does not provide (yet) the means to store changed key/values. I'd suggest you to have a look at the preferences plugin (http://griffon-plugins.github.io/griffon-preferences-plugin/). This plugin won't let you persist changes made to Configuration though, you must create a preferences aware object (most likely a Model) for example

class MainModel {
  @Preference int windowX
  @Preference int windowY
  @Preference int windowHeight
  @Preference int windowWidth
}

Make sure to reference these model properties in the View when the Window is created and configured. I'd recommend to use bindings to update these values based on changes made by the user when dragging/resizing the window. Don't forget to explicitly persist changes by calling `preferencesManager.save(model)`.

Cheers,
Andres
Reply | Threaded
Open this post in threaded view
|

Re: Questions about modular applications with Griffon

Marcelo
This post was updated on .
Hi Andres,

Thanks for the answer.

I do need multiple windows at application startup without interaction from the user. The use case is that the application remembers all the windows opened, position, size and whatever other information the view needs at startup. Each window is provided by a subproject via the module/addon mechanism. Each subproject define a compile dependency to a subproject that launches the application: launcher 

The graddle configuration in launcher declares a runtime dependency to all the other subprojects in the application. With this approach, if I add a subproject to the application that defines new startup windows, I do not need to touch the code in launcher.

Anyway, what I ended up doing, but I don't know if it is the best approach is:

1- I defined the interface StartupWindowGriffonAddon with the method
public List<String> getStartupWindows()
 that return the names of the windows that need to be opened at startup. Each Griffon addon that provides startup windows implement that interface.

2- Defined an inexistent starting window name to prevent windowManager.startingWindow from throwing the ArrayIndexOutOfBoundException.

3- In the lifecycle hook Startup.java in the launcher subproject, I cycle through the GriffonAddons the application define, check if they implement StartupWindowGriffonAddon and, if they do, instruct WindowsManager to show the window.

Thanks,

Marcelo.
Reply | Threaded
Open this post in threaded view
|

Re: Questions about modular applications with Griffon

aalmiray
Administrator
Hi Marcelo,

I see. This is a good approach, except for step #2 which appears to be a workaround for a bug in Griffon (we'll fix it for the next release).

Cheers,
Andres

https://github.com/griffon/griffon/issues/203
Reply | Threaded
Open this post in threaded view
|

Re: Questions about modular applications with Griffon

Marcelo
Thanks Andres!
Reply | Threaded
Open this post in threaded view
|

Re: Questions about modular applications with Griffon

aalmiray
Administrator
Hi Marcelo,

If the application's configuration (Config.groovy or Config.properties) is found in the *launcher* project then yes, you may need to set it up as a test dependency as you mentioned. However there's an alternative: in order to run integration/functional tests you only need a working application. It does not have to be the real application.

What does this mean? You can define a dummy/simplified application configuration file, resources, and additional files as part of src/test/resources inside the target subproject. This technique is used by several griffon plugins where a "real" application is not available but you still want to run tests using the Griffon @Rule classes.

The default application bootstrap mechanism expects a configuration resource bundle to exist. It may be Config.properties (default if running plain Java) or Config.groovy (if you've added the griffon-groovy dependency). This file needs to exist but can be left empty for testing purposes, see https://github.com/griffon-plugins/griffon-preferences-plugin/blob/master/subprojects/griffon-preferences-core/src/test/resources/Config.properties for example.

I'm afraid Jukito can't be used in combination with any of the Griffon @Rule classes, as it initializes Guice directly where as Griffon relies on its own JSR-330 layer. It's possible to use Mockito directly though as shown at https://github.com/griffon/griffon/tree/development/tutorials/patterns/pmvc (design explained at http://griffon-framework.org/tutorials/5_mvc_patterns.html).

Cheers,
Andres
Reply | Threaded
Open this post in threaded view
|

Re: Questions about modular applications with Griffon

Marcelo
Hi Andres,

Thanks for all the information. It took me a while debugging and going through the code, and then I saw your answer to my question in StackOverflow! That pointed me to the right direction: I was getting errors running the functional and integration tests because of the missing configuration files...

Tests are beautifully decoupled now, but I still see a strange effect, it seems the tests are still using the configuration saved by the preferences plugin. I used a shutdown hook (Shutdown.java) that I have in the launcher subproject to actually persist the modified configuration. When I run the functional tests accessing the GUI, somehow the stage position reflects the persisted values used when the application runs, and I get a message in the output window saying the configuration is persisted after each test runs. I was expecting the scene to be centered on the screen and the configuration to be untouched by the test. Do you know what might be the cause?

Thanks again,

Marcelo.
Reply | Threaded
Open this post in threaded view
|

Re: Questions about modular applications with Griffon

aalmiray
Administrator
Hi Marcelo,

That effect is due to the difference between integration and functional tests. Integration tests allow you to insert fake/mock collaborators using modules; functional tests otoh do not, they rely on the real production code to do their job. This means you can override the PreferencesPersistor in an integration test, loading/saving preferences to a different location; functional tests will use the real location and the real persistor.

Yet, when tests are run the griffon Environment is set to "test". When running the application in development mode Environment is set to "dev". When running the application from a distribution Environment is set to "prod". You may use this fact to alter (or disregard) the values read from he preferences storage, also to avoid persisting test data. This is exactly the use case the Environment class was designed to fulfill.

Cheers,
Andres
Reply | Threaded
Open this post in threaded view
|

Re: Questions about modular applications with Griffon

Marcelo
Thanks Andres. All clear now!
Reply | Threaded
Open this post in threaded view
|

Re: Questions about modular applications with Griffon

aalmiray
Administrator
Please note that you may go one step further by creating a custom PreferencesPersistorProvider that has `griffon.core.env.Environment` injected into it. You'll then apply the environment query logic to the body of the `get()` method, choosing the right persistor as a result. This guarantees the persistor is set once (if the binding is defined as Singleton).

Cheers,
Andres