Gennadiy Donchyts Just another WordPress weblog

3Feb/100

Is it good or bad practice to use events in Domain Classes?

Why to use events in entities?

.NET provides a native implementation of observer / subscriber pattern by means of events. I tried to use them not only in the UI classes but also to manage changes in the entities (see info about DDD for a definition of an entity). A lot of Entities in our application are in most cases bound to one or many controls. Entities are composed with other entities, they get changed, all these changes need to be monitored so that corresponding views will be updated or any other actions will be invoked.

Events in .NET is something to be used very carefully. Otherwise you'll end up with too many events :) . Finally we ended up with the two types of the events which are used in the entities and can be considered as Domain Events:

1. NotifyPropertyChanged - fired when any property changes, e.g. when name of the branch is changed - tree node needs to be updated, if branch is selected - it's name in the property grid control needs to be updated, etc.

2. NotifyCollectionChanged - fired when any collection is changed, add/remove item

Definition of Domain Event comes from the following post of Martin Fowler: http://martinfowler.com/eaaDev/DomainEvent.html

Once domain model is fully covered by events,  implementation of Undo / Redo functionality becomes trivial task (Memento pattern + these 2 types of events).

After a few tries we ended up with the following solution:

  • All objects implement INotifyPropertyChanged for all entities
  • All collections used in the properties implement INotifyCollectionChange, using interface IEventedList<T>: IList<T>, INotifyCollectionChanged
  • When events are fired in the child objects - they are bubbled to the higher level object

Of course implementing it by hand in every domain class is a violation of the DRY principle, so PostSharp were used to implement such behavior automatically by adding a few attributes on top of the entity class.

Example

Let's look on a very simple example, a blog containing blog posts and comments.

Without events it may look like this:

When events are used - there is simply no need for controller, everything can be bound directly to the view controls:

In this case view (or a very simple controller) subscribes to the changes in the domain model and then refreshes what needs to be refreshed.

Of course in reality there are some more issues, like:

  • Clients need to subscribe only to the events they need to use (filter)
  • Memory leak issues, solution: use weak events where possible http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
  • Sometimes events need to be grouped or filtered (10000 items added to collection will generate 10000 events and therefore 10000 calls, if there are no subscribers - overhead is not that big, ~20% compare to the plain calls)

But in any case if events are used in as shown here - much less source code need to be produced plus data model always remain in sync with all views.

How events are fired and handled when blog title changes:

How events are fired and routed when new post is added:

Filed under: Uncategorized No Comments
20Dec/090

Fitnesse4net source code is available on Google Code

I've uploaded source code for .NET version of FitNesse4Net to Google Code. It is simply original version of FitNesse but converted to .NET using IKVM.NET and then extended with a small wrapper allowing to use default test runner to run .NET tests. Also command-line is a bit extended so that it can run as a server or command-line runner. See source code and binaries here: FitNesse4Net on Google Code

Filed under: FitNesse, Test No Comments
10Dec/090

Just learned from ReSharper how to make code look nicer

ReSharper really helps to learn how to code. During writing a test for a ForEach extension method for IEnumerable<T> it suggested to improve the following code:

ReSharper_group1

The resulting code got much better:

ReSharper_group2

7Dec/090

Update version of FitNesse for .NET.

This time a command-line and MSBuild runners are included: fitnesse4net-bin.zip 8.3Mb

After experimenting a bit more with FitNesse I managed to make it run .NET assemblies with about 15Kb of source code injected directly into FitNesse internals :) . It not tested much so probably some bugs will need to be fixed, but it is able to run demo .NET assemblies without any problems. The main purpose was proof of concept, to see if it will work at all and try to develop a MSBuild tasks allowing to run all tests using command-line.

What is included:

fitnesse4net

DemoTest1 page contains the following text:

!contents
!|script|DemoProject.MultiplierBy2|
 |input|2|
 |run|
 |check|output|4|
 !|script| DemoProject.MultiplierBy2|
 |input|2|
 |run|
 |check|output|5|

The file content.txt in FitNesseRoot/ is used to find a list of assemblies to be referenced, looks like this:

!path ..\DemoProject\bin\Debug\DemoProject.dll

After run of run-test-suite.cmd the following output should be printed and the target/ directory should contain results of the tests as html.

Loading assembly ..\DemoProject\bin\Debug\DemoProject.dll ...
Running test suite FrontPage.DemoSuite ...
Running test FrontPage.DemoSuite.DemoTest1 ...
Test results: 3 right, 1 wrong, 0 ignored, 0 exceptions
Running test FrontPage.DemoSuite.DemoTest2 ...
Test results: 4 right, 0 wrong, 0 ignored, 0 exceptions
fitnesse4net-results

Contents of FitNesse.proj:

<Project>
<UsingTask TaskName="Tasks.RunSlimTestSuite" AssemblyFile="..\bin\FitNesse4Net.MSBuidTasks.dll" />
<UsingTask TaskName="Tasks.RunSlimTest" AssemblyFile="..\bin\FitNesse4Net.MSBuidTasks.dll" />

<Target Name="RunSlimTestSuite">
   <RunSlimTestSuite WorkingDirectory=".\src\DemoProject.Tests" TargetDirectory=".\target" TestSuiteName="FrontPage.DemoSuite" />
</Target>

<Target Name="RunSlimTest">
   <RunSlimTest WorkingDirectory=".\src\DemoProject.Tests" TargetDirectory=".\target" TestName="FrontPage.DemoSuite.DemoTest1" />
   <RunSlimTest WorkingDirectory=".\src\DemoProject.Tests" TargetDirectory=".\target" TestName="FrontPage.DemoSuite.DemoTest2" />
</Target>

</Project>

msbuild-run-test-suite.cmd:

C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe FitNesse.proj /t:RunSlimTest

msbuild-run-test.cmd:

C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe FitNesse.proj /t:RunSlimTestSuite

run-test-suite.cmd:

..\bin\FitNesse4Net.exe --run-test-suite=FrontPage.DemoSuite --work-directory=.\src\DemoProject.Tests --target-directory=.\target

run-test.cmd:

..\bin\FitNesse4Net.exe --run-test=FrontPage.DemoSuite.DemoTest1 --work-directory=.\src\DemoProject.Tests --target-directory=.\target

start-server.cmd:

start ..\bin\FitNesse4Net.exe --port=8080 --work-directory=.\src\DemoProject.Tests

stop-server.cmd:

..\bin\FitNesse4Net.exe --shutdown --port=8080

After FitNesse4Net.exe is started without arguments, usage help will be printed to the console. Many options are similar to those provided by Java fitnesse.jar but some are new like --shutdown, --run-test-suite, --run-test

Note: after run of start-server.cmd - it will generate a lot of other files in your FitNesseRoot.

fitnesse4net-bin/
|
|   README
|
+---bin
|       fitnesse.dll .......................... original java fitnesse.jar
|       FitNesse4Net.exe ...................... main .NET executable
|       FitNesse4Net.MSBuidTasks.dll
|       IKVM.OpenJDK.Core.dll
|       IKVM.OpenJDK.Misc.dll
|       IKVM.OpenJDK.Security.dll
|       IKVM.OpenJDK.Text.dll
|       IKVM.OpenJDK.Util.dll
|       IKVM.OpenJDK.XML.dll
|       IKVM.Runtime.dll
|       log4net.dll
|       NDesk.Options.dll
|
\---examples
|   FitNesse.proj
|   msbuild-run-test-suite.cmd .............. example scripts showing how to run FitNesse
|   msbuild-run-test.cmd                      using command-line runner or MSBuild
|   run-test-suite.cmd
|   run-test.cmd
|   start-server.cmd
|   stop-server.cmd
|
\---src
+---DemoProject
|   |   DemoProject.csproj
|   |   MultiplierBy2.cs
|   |
|   +---bin
|   |   \---Debug
|   |           DemoProject.dll .......... system under test
|   |           DemoProject.pdb
|   |
|   \---Properties
|           AssemblyInfo.cs
|
\---DemoProject.Tests
|
|
\---FitNesseRoot
|   content.txt
|   properties.xml
|
+---files ......................... some files required to generate reports
|
\---FrontPage
|   content.txt
|   properties.xml
|
\---DemoSuite ................. demo test suite
|   content.txt
|   properties.xml
|
+---DemoTest1
|       content.txt
|       properties.xml
|
\---DemoTest2
content.txt
properties.xml
DemoTest1 contains the following text:
!contents
!|script|DemoProject.MultiplierBy2|
|input|2|
|run|
|check|output|4|
!|script| DemoProject.MultiplierBy2|
|input|2|
|run|
|check|output|5|
content.txt in FitNesseRoot is used to find a list of assemblies to be referenced. Usually it looks like:
!path ..\DemoProject\bin\Debug\DemoProject.dll
After run of run-test-suite.cmd the target/ directory will contain results of the tests as html.
...
Note1: after run of start-server.cmd - it will generate a lot of other files in your FitNesseRoot.
FitNesse.proj:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="Tasks.RunSlimTestSuite" AssemblyFile="..\bin\FitNesse4Net.MSBuidTasks.dll" />
<UsingTask TaskName="Tasks.RunSlimTest" AssemblyFile="..\bin\FitNesse4Net.MSBuidTasks.dll" />
<Target Name="RunSlimTestSuite">
<RunSlimTestSuite WorkingDirectory=".\src\DemoProject.Tests" TargetDirectory=".\target" TestSuiteName="FrontPage.DemoSuite" />
</Target>
<Target Name="RunSlimTest">
<RunSlimTest WorkingDirectory=".\src\DemoProject.Tests" TargetDirectory=".\target" TestName="FrontPage.DemoSuite.DemoTest1" />
<RunSlimTest WorkingDirectory=".\src\DemoProject.Tests" TargetDirectory=".\target" TestName="FrontPage.DemoSuite.DemoTest2" />
</Target>
</Project>
msbuild-run-test-suite.cmd:
C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe FitNesse.proj /t:RunSlimTest
msbuild-run-test.cmd:
C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe FitNesse.proj /t:RunSlimTestSuite
run-test-suite.cmd:
..\bin\FitNesse4Net.exe --run-test-suite=FrontPage.DemoSuite --work-directory=.\src\DemoProject.Tests --target-directory=.\target
run-test.cmd:
..\bin\FitNesse4Net.exe --run-test=FrontPage.DemoSuite.DemoTest1 --work-directory=.\src\DemoProject.Tests --target-directory=.\target
start-server.cmd:
start ..\bin\FitNesse4Net.exe --port=8080 --work-directory=.\src\DemoProject.Tests
stop-server.cmd:
..\bin\FitNesse4Net.exe --shutdown --port=8080

After FitNesse4Net.exe is stared, a list of possible options will be printed to the console. Many options a similar to those provided by Java fitnesse.jar but some are new like --shutdown, --run-test-suite, --run-test

TODO: check if it will work as a test runner from FitNesse wiki (currently it will say that runner is not found if you try to run tests from the fitnesse wiki).

Filed under: Test No Comments
6Dec/090

Got stack overflow on StackOverflow :)

StackOverflow

Filed under: Uncategorized No Comments
1Dec/091

Using FitNesse under .NET without JRE

Not sure if anyone will use it but it was kind of funny thing to do. After playing with FitNesse (Function Integration Testing Framework) which is mainly developed in Java I check if it can be converted to .NET using IKVM and the trick seems to work, you can start server, edit pages and runs tests. It is simply packaged version of fitnesse.jar and .NET runner by by Mike Stockdale. ... uses pure Java version.

Download: fitnesse4net.zip 8.7Mb

Zip file contains only .NET binaries required to run FitNesse and small demo project containing a single class. No Java is required.

After starting run_server.cmd you have to wait until FitNesse will unpack all it's files into FitNesseRoot, then press refresh in browser. After you run demo test on a root page you should get something like this (the web page has been updated, see another blog post for a new version):

fitnesse-results

It works a bit slower compare to Java but still seems to be stable.

Directory structure:

fit4net-directory

fit4net
|
+---bin
|       fit.dll
|       fitnesse.exe ................ FitNesse all in one wiki/server
|       fitSharp.dll
|       IKVM.OpenJDK.Core.dll
|       IKVM.OpenJDK.Misc.dll
|       IKVM.OpenJDK.Security.dll
|       IKVM.OpenJDK.Text.dll
|       IKVM.OpenJDK.Util.dll
|       IKVM.OpenJDK.XML.dll
|       IKVM.Runtime.dll
|       Runner.exe .................. command-line test runner, fetches content from server
|       RunnerW.exe ................. same as previous, but with window
|
\---example
|   run_server.cmd
|
+---FitNesseRoot
|       content.txt
|       properties.xml
|
\---src
|   DemoFitNesseTests.csproj
|   DemoFitNesseTests.sln
|   DemoMultiplierBy2.cs
|
\---bin
\---Debug
DemoFitNesseTests.dll

Right now I'm struggling trying to integrate it with MSBuild / TeamCity, looks like nobody implemented it yet :( DONE

Filed under: Uncategorized 1 Comment
31Jul/090

Making NHibernate lazy-loading PostSharp-ed objects work.

I'm using PostSharp to add implementation of INotifyPropertyChanged in AOP way using PostSharp compound aspect.

At the same time object has to be saved into a database and loaded in a lazy way (NHibernate + LinFu as a DynamicProxy). It seems that when object is loaded - all aspects disappear in a proxy object created by a DynamicProxy. All required fields are present in the proxy type but aspects are not initialized and PostSharp  instanceCredentials = {0}.

Gael Fraiteur sent me a message after I Twittered about the problem (so, Twitter works! :) ):

@gena_d Look at http://tiny.cc/zIXFT. Call this method: LaosUtils.InitializeCurrentAspects() if the aspect constructor is skipped.

Thanks! But unfortunatelly it was not a solution for me since object even has no aspect implementation injected into it, it happens in the constructor which is skipped :( .

QuickWatch_LazyAOPObject

After debugging deep into NHibernate and LinFu I've found a workaround:

  1. Listen to the ILoadEventListener when lazy object is initialized by NHibernate
  2. During load, inject another instance of NotifyPropertyChangedImplementation into proxy and forward event from underlying object to that implementation.
public void OnLoad(LoadEvent @event, LoadType loadType)
{
    defaultLoadEventListener.OnLoad(@event, loadType);

    InitializePropertyChangedAspect(@event.Result);
}

///
/// Connects property changed events of the proxy object to the real object
/// so when PropertyChanged event is fired - it will be redirected to the proxy.
///
///

private void InitializePropertyChangedAspect(object o)
{
    if (!(o is INHibernateProxy) || !(o is IComposed))
    {
        return;
    }

    var proxyObject = (INHibernateProxy) o;
    var lazyInitializer = proxyObject.HibernateLazyInitializer;
    var realObject = lazyInitializer.GetImplementation();

    // initialized property changed aspect implementation
    // and redirects events fired in the wrapped object
    // to the aspect implementation in the parent object (proxy)
    var aspect = new NotifyPropertyChangedImplementation(o, InstanceCredentials.Null, false);
    ((IComposed) o).SetImplementation(InstanceCredentials.Null, aspect);

    var notifiable = (INotifyPropertyChanged) realObject;
    notifiable.PropertyChanged += ((sender, e) => aspect.OnPropertyChanged(proxyObject, e));
}

As a result a client code subscribing to a proxy object can listen to a PropertyChanged event. The code looks a bit ugly, it would nicer if DynamicProxy can handle these events.

After implementation of aspect is injected - the proxy object looks like this:

Filed under: Uncategorized No Comments
30Mar/091

SPAM

Или как же меня достало удалять эти комменты, кто бы им интернет зафайерволил в конце-концов, поставил вот что-то под названием clickacha, чтобы им жизнь немного усложнить.

update: clickacha не работает, удалил и поставил обычную Capatcha

Filed under: Uncategorized 1 Comment
22Mar/090

Upgraded jar2ikvmc to ikvm 0.38.0.2

Just upgraded jar2ikvmc to the last version of ikvm. The most annoying problem was with conversion of JarAnalyzer to .NET, a resource file called Filter.properties was missing. The problem was solved after I embedded it by hand into JarAnalyzer.jar and then converted using ikvmc.exe.

The new version is uploaded to jar2ikvmc on Google code page.

Filed under: jar2ikvmc No Comments
28Oct/07388

Minor release. Moved to Google Code.

Today I moved all sources and binaries to Google Code. It seems to work fine for such a small tool :) .
Please use Issues section there to submit bug fixes or suggestions.
Also a new release was created combined with latest version of ikvm: ikvmbin-0.37.2855.zip.

From now tool will be distributed using the following naming convention:
jar2ikvmc-<major>.<minor>.<build>.zip

Build number is actually equal to tagged revision number in subversion.

Binary: jar2ikvmc-0.1.7.zip
Source: jar2ikvmc-0.1.7-src.zip

Filed under: jar2ikvmc 388 Comments