Validating sending mail messages in smart use case unit tests

When building applications with the Adf framework, smart use cases are implemented in task classes. Quite regularly mail messages are sent from tasks. To do so we use the MailManager class. Using this class mail messages are usually build up as in the following code example.

image

To send mail messages, the MailManager plugs in an implementation of the IMailProvider interface. Currently, Adf provides two mail providers, the obvious implementation SmtpMailProvider, and the DummyMailProvider, which creates the mail messages and dumps it in a specified directory.

Smart use cases, implemented in task classes, are always unit tested. Here, each of the public methods of the tasks are tested using the Adf.Test libraries from the framework. A number of test methods is usually implemented to test all possible scenario’s of going through the smart use cases. In the following code example, such as method is shown.

image

In this example, the Init() method of the task is called. After it is called the test framework can perform any number of checks. In this example, all validations should succeed, and the Status property of the Account domain object should be set to Unverified. The last validation ViewIsActivated will check if the accompanying (web, Windows RT) page or Windows form is presented to the user.

Recently, we have added new features to also validate whether a task has sent the mail message it was supposed to send. To provide for this functionality, we’ve added a TestMailProvider to the Adf.Test libraries. In your test project, this mail provider needs to be plugged in to the MailManager, as follows in code (or in the app.config file).

image

This will ensure that any mail messages being send by the tasks, is sent through the TestMailProvider. The test mail provider will, similar to the DummyMailProvider, place mail messages in a specified directory, but foremost it will also notify the TestManager that a message was sent.

Next, when unit testing the method that either sends, or doesn’t send the mail messages, you will be able to verify this, as in the following code example.

image

You can use the MailIsSent or the MailIsNotSent methods for this means.

Reaching post-conditions in tasks

Implementing use cases in Adf.Net is covered by the task pattern. Each smart use case in the model is implemented as a descendant of the Task class in Adf.Net.

The task pattern consists of three major parts:

  • Starting the task, either using a parameterized Init() method, or the default Start() method.
  • After a task calls another task, and this second task is finished, control goes back to the calling task, using any of the specific ContinueFrom() methods, or the default Continue() method.
  • When a task reaches any of the post-conditions of a task, the task needs to be ended, usually through the Ok() or Cancel() method.

In this specific post I’ll look at reaching the post-conditions. In the pattern, the Task class itself acts as the layer super type and implements a number of method to end itself. A use case can have multiple post-conditions. Some of them are positive, some can be negative. And sometimes a specific case needs to be addressed.

When a reaches it positive post-condition, it is a best practices to end it using the Ok() method. This method takes a params object[] p, which allows you to pass back results from the task to the calling task, using the following signature.

public virtual void OK(params object[] p);

A similar method Cancel() exists for task that end in a negative post-condition, usually because the user cancels the interaction:

public virtual void Cancel(params object[] p);

But, Adf.Net also supplies a similar method Error(), to allow tasks to finish with an error, possibly technical.

Under the covers, these three methods call on another method, which is called Finish(). This method takes care of passing back an instance of TaskResult. This result also gets posted back to the calling task, so it knows how the called task has ended. As in the following example, you could also use Finish() yourself.

if (persoon.IsNullOrEmpty())
{
    ValidationManager.AddError(GEENPERSOONGESELECTEERD);
    Finish(TaskResult.Error);
}

Please note that the code above is equal to the following code.

if (persoon.IsNullOrEmpty())
{
    ValidationManager.AddError(GEENPERSOONGESELECTEERD);
    Error();
}

By default, TaskResult has the following values in Adf.Net: Ok, Cancel, Error.

However, as TaskResult is implemented using the descriptor pattern, additional project specific value can be added easily, by inheriting from TaskResult, and added the specific values. Thus you would be able to end your task in more project specific ways, such as in the code example below.

if (persoon.IsNullOrEmpty())
{
    ValidationManager.AddError(GEENPERSOONGESELECTEERD);
    Finish(Dashboard8TaskResult.Aborted);
}

A short notice about object relational mapping framework generated queries…

I guess object relational mapping is an accepted paradigm for exchanging data between an object oriented domain layer and underlying databases. For most applications object relational mapping is more than sufficient. And if not, perhaps command query responsibility segregation might contribute well to your solution.

Being a more than frequent user, this very short blog post is not meant to question either of these popular paradigms. However, I would like to make it clear that applying object relational mapping might not always lead to optimal solutions, especially from a database perspective. Most object relational mapping framework generate the underlying queries to the database, based on the domain layer and the associations between the domain objects in this layer. The quality of these generated queries is strongly dependent on the quality of the domain model and layer. If the domain model is complex, so will the generated queries be. Rubbish in is rubbish out.

image

So it is noteworthy how the framework you’re using maps associations and properties to SQL statements. Some frameworks may choose remarkable construct for this mapping resulting in hard and unreadable queries, or queries that aren’t very much optimized for the underlying data model. So this short blog post is just to make you notice.

The problem actually comes from the enormous amount of features a full-grown object relational mapping framework needs to implement. Basically, everything and the kitchen sink. Driven by the good but blind ambition to serve as many developers as they possibly can, in any case more than all other competitors, the framework developers implement as many features as the code in the framework can handle. And sometimes this feature bloating may lead to peculiar results, where the framework might still function, but the original goals of providing a lightweight, straightforward, easy-to-use framework to solve most common mapping problems have long been abandoned.

For developers using such frameworks I guess the most decent piece of advice I can give is: don’t go into the dark corners of domain modeling, as the solutions resulting may be hard to maintain and to extend. Use middle-of-the-road constructs, and prefer simpler, more generic out-of-the-box solutions to more specific and elaborate domain models.

I’ll leave with an example of a query which was generated by a frequently used object relational mapping framework in the .Net space. I’m quite sure the database will have a hard time executing this particular query.

And yes, the following is indeed a single select query. Go figure.

SELECT

[Extent1].[ID] AS [ID],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN ’2X’ WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1) AND([UnionAll2].[C6] IS NOT NULL)))) THEN ’2X0X’ WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOT NULL)) THEN ’2X0X0X’ WHEN(([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT (([UnionAll1].[C6] = 1) AND ([UnionAll1].[C6] IS NOT NULL)))) THEN’2X1X’ ELSE ’2X1X0X’ END AS [C1],

[Extent1].[MAPReportId] AS [MAPReportId],

[Extent1].[PartnerNumber] AS [PartnerNumber],

[Extent1].[CapturedVia] AS [CapturedVia],

[Extent1].[Currency] AS [Currency],

[Extent1].[DataAsOf] AS [DataAsOf],

[Extent1].[DateApproved] AS [DateApproved],

[Extent1].[DateCreated] AS [DateCreated],

[Extent1].[DateProcessed] AS [DateProcessed],

[Extent1].[DateReturned] AS [DateReturned],

[Extent1].[DateSubmittedToRo] AS [DateSubmittedToRo],

[Extent1].[PeriodCovered] AS [PeriodCovered],

[Extent1].[ProcessGeneralComments] AS [ProcessGeneralComments],

[Extent1].[ReasonReturned] AS [ReasonReturned],

[Extent1].[StatusID] AS [StatusID],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1)AND ([UnionAll2].[C6] IS NOT NULL)))) THEN [UnionAll1].[AverageLoanSize] WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOT NULL))THEN [UnionAll1].[AverageLoanSize] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT (([UnionAll1].[C6] = 1) AND([UnionAll1].[C6] IS NOT NULL)))) THEN CAST(NULL AS float) END AS [C2],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1)AND ([UnionAll2].[C6] IS NOT NULL)))) THEN [UnionAll1].[AveragePortfolioOutstanding] WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6]IS NOT NULL)) THEN [UnionAll1].[AveragePortfolioOutstanding] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT(([UnionAll1].[C6] = 1) AND ([UnionAll1].[C6] IS NOT NULL)))) THEN CAST(NULL AS float) END AS [C3],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1)AND ([UnionAll2].[C6] IS NOT NULL)))) THEN [UnionAll1].[BoYPortfolioOutstanding] WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] ISNOT NULL)) THEN [UnionAll1].[BoYPortfolioOutstanding] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT(([UnionAll1].[C6] = 1) AND ([UnionAll1].[C6] IS NOT NULL)))) THEN CAST(NULL AS float) END AS [C4],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1)AND ([UnionAll2].[C6] IS NOT NULL)))) THEN [UnionAll1].[DepositsAmount] WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOT NULL))THEN [UnionAll1].[DepositsAmount] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT (([UnionAll1].[C6] = 1) AND([UnionAll1].[C6] IS NOT NULL)))) THEN CAST(NULL AS float) END AS [C5],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS int) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1) AND([UnionAll2].[C6] IS NOT NULL)))) THEN [UnionAll1].[EoYNrBorrowers] WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOT NULL)) THEN[UnionAll1].[EoYNrBorrowers] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT (([UnionAll1].[C6] = 1) AND([UnionAll1].[C6] IS NOT NULL)))) THEN CAST(NULL AS int) END AS [C6],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1)AND ([UnionAll2].[C6] IS NOT NULL)))) THEN [UnionAll1].[EoYPortfolioOutstanding] WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] ISNOT NULL)) THEN [UnionAll1].[EoYPortfolioOutstanding] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT(([UnionAll1].[C6] = 1) AND ([UnionAll1].[C6] IS NOT NULL)))) THEN CAST(NULL AS float) END AS [C7],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS varchar(1)) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] =1) AND ([UnionAll2].[C6] IS NOT NULL)))) THEN [UnionAll1].[MFIPortfolioSize] WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOTNULL)) THEN [UnionAll1].[MFIPortfolioSize] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT (([UnionAll1].[C6] =1) AND ([UnionAll1].[C6] IS NOT NULL)))) THEN CAST(NULL AS varchar(1)) END AS [C8],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS int) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1) AND([UnionAll2].[C6] IS NOT NULL)))) THEN [UnionAll1].[NrLoans] WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOT NULL)) THEN[UnionAll1].[NrLoans] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT (([UnionAll1].[C6] = 1) AND([UnionAll1].[C6] IS NOT NULL)))) THEN CAST(NULL AS int) END AS [C9],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1)AND ([UnionAll2].[C6] IS NOT NULL)))) THEN [UnionAll1].[InvestmentOutstanding] WHEN (([UnionAll2].[C6] = 1) AND([UnionAll2].[C6] IS NOT NULL)) THEN [UnionAll1].[InvestmentOutstanding] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL) AND ( NOT (([UnionAll1].[C6] = 1) AND ([UnionAll1].[C6] IS NOT NULL)))) THEN CAST(NULL AS float) END AS [C10],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1)AND ([UnionAll2].[C6] IS NOT NULL)))) THEN [UnionAll1].[LoanOutstanding] WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] ISNOT NULL)) THEN [UnionAll1].[LoanOutstanding] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT(([UnionAll1].[C6] = 1) AND ([UnionAll1].[C6] IS NOT NULL)))) THEN CAST(NULL AS float) END AS [C11],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1)AND ([UnionAll2].[C6] IS NOT NULL)))) THEN [UnionAll1].[OutstandingExposure] WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOTNULL)) THEN [UnionAll1].[OutstandingExposure] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT(([UnionAll1].[C6] = 1) AND ([UnionAll1].[C6] IS NOT NULL)))) THEN CAST(NULL AS float) END AS [C12],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS varchar(1)) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] =1) AND ([UnionAll2].[C6] IS NOT NULL)))) THEN CAST(NULL AS varchar(1)) WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOT NULL))THEN [UnionAll2].[C1] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT (([UnionAll1].[C6] = 1) AND([UnionAll1].[C6] IS NOT NULL)))) THEN CAST(NULL AS varchar(1)) END AS [C13],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1)AND ([UnionAll2].[C6] IS NOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOT NULL)) THEN[UnionAll2].[C2] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT (([UnionAll1].[C6] = 1) AND ([UnionAll1].[C6]IS NOT NULL)))) THEN CAST(NULL AS float) END AS [C14],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1)AND ([UnionAll2].[C6] IS NOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOT NULL)) THEN[UnionAll2].[C3] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT (([UnionAll1].[C6] = 1) AND ([UnionAll1].[C6]IS NOT NULL)))) THEN CAST(NULL AS float) END AS [C15],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS varchar(1)) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] =1) AND ([UnionAll2].[C6] IS NOT NULL)))) THEN CAST(NULL AS varchar(1)) WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOT NULL))THEN [UnionAll2].[C4] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT (([UnionAll1].[C6] = 1) AND([UnionAll1].[C6] IS NOT NULL)))) THEN CAST(NULL AS varchar(1)) END AS [C16],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS bit) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1) AND([UnionAll2].[C6] IS NOT NULL)))) THEN CAST(NULL AS bit) WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOT NULL)) THEN[UnionAll2].[C5] WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT (([UnionAll1].[C6] = 1) AND ([UnionAll1].[C6]IS NOT NULL)))) THEN CAST(NULL AS bit) END AS [C17],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1)AND ([UnionAll2].[C6] IS NOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOT NULL)) THENCAST(NULL AS float) WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT (([UnionAll1].[C6] = 1) AND([UnionAll1].[C6] IS NOT NULL)))) THEN [UnionAll2].[Exposure] ELSE [UnionAll2].[Exposure] END AS [C18],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1)AND ([UnionAll2].[C6] IS NOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOT NULL)) THENCAST(NULL AS float) WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT (([UnionAll1].[C6] = 1) AND([UnionAll1].[C6] IS NOT NULL)))) THEN [UnionAll2].[InvestmentOutstanding] ELSE [UnionAll2].[InvestmentOutstanding]END AS [C19],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT (([UnionAll2].[C6] = 1)AND ([UnionAll2].[C6] IS NOT NULL)))) THEN CAST(NULL AS float) WHEN (([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOT NULL)) THENCAST(NULL AS float) WHEN (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] IS NOT NULL) AND ( NOT (([UnionAll1].[C6] = 1) AND([UnionAll1].[C6] IS NOT NULL)))) THEN [UnionAll2].[LoanOutstanding] ELSE [UnionAll2].[LoanOutstanding] END AS [C20],

CASE WHEN (( NOT (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL))) AND ( NOT (([UnionAll2].[C7] = 1) AND ([UnionAll2].[C7] ISNOT NULL)))) THEN CAST(NULL AS decimal(18,2)) WHEN (([UnionAll1].[C5] = 1) AND ([UnionAll1].[C5] IS NOT NULL) AND ( NOT(([UnionAll2].[C6] = 1) AND ([UnionAll2].[C6] IS NOT NULL)))) THEN CAST(NULL AS decimal(18,2)) WHEN (([UnionAll2].[C6] = 1) AND([UnionAll2].[C6] IS NOT NULL))

How Smart Use Cases Can Drive Web Development. Video for session at DevDays 2011 [in Dutch]

as the Channel 9 website says: using real-life code examples Sander will demonstrate how to model, generate and build smart use cases and introduce the positive impact smart use cases have on your layered software architecture.

Anyway, here’s the video for my DevDays 2011 session:

A book on pragmatic software architecture, patterns and frameworks?

One of the major items on my wish list – that is on the professional half of it – is to write a book that displays my ideas on software architecture, patterns and frameworks. Yes I know, there are many books on software architecture, and there are many books that explain patterns, and yes there also are a lot of books that show you how to work with framework A, B or C.

So you would say, why on earth would you want to write a book on these subjects? Well there’s three major reasons. First, there’s not a lot of books out there that combine all these subjects into one single coherent story that will actually help you to write your applications better. Second, maybe I’m a bit arrogant on this, but I feel like I have something to all the stuff that’s out there. And last but not least, I finally need to get this stuff out of my head! Maybe ambitious, but let´s see!

Order from the menu

To be honest, you might like or not like all of the ideas in this book, or even may totally disagree with some of them. Actually, you probably will. But on the whole, I hope this book will be able to inspire you and illuminate you in how to build software. It´s often a matter of taste. So consider this book as a menu. Just order from it what you like. You don´t need to copy the C# and .NET code examples from the book, but rather I hope that use can use them to shape your own ideas on software architecture, frameworks and patterns. Take from it what you like, and forget about the few things you don’t like. 

A first step

What’s the first step towards writing this book? I’ve been teaching about software architecture, patterns and frameworks for .NET for quite a number of years, and the material from these courses will be a good start, similar to the way I shaped my book on UML – from iterating over the material I had for my UML courses.

PIC091019003 

Along with the course material comes the work I’ve done in projects, and as a result the work we (the core team) do on our ever-evolving and improving ADF.NET framework.

Architecture and frameworks

So here’s the overview of the material in the software architecture and patterns course:

  • Introduction. All the usual bla bla and thanks you stuff.
  • How to set up your software architecture? A mild and brief introduction. Thinking about layers, elements in layers,  collaborations (yes CRC). And of cause you can not start without summing up some of the good practices, such as
  • (Software architecture != List<Framework>). How people mix up architecture and frameworks.
  • How frameworks can kill your project. It’s all about dependencies guys. This chapter will be a write out of my talk with the same name.
  • Some patterns to help avoid getting killed. Explaining the different variations of layer super types, my fine descriptor pattern, and the use of dependency injection and the manager-provider pattern.
  • Glue 1.0. My first framework. Short introduction into setting up a minimal framework, using your architecture and the above patterns, minimizing dependencies between your application code and the frameworks you use. It should provide you with the glue to keep your “regular” developers in the loop.
  • Extension methods and fluent interfaces. Have got a bit (not all by far!) to say on how to use them to enable your application developers to code easier and more natural using your first framework.
  • Lasagna and ingredients. What layers would you need in your application? Suggesting presentation, process, domain and data/services. And suggesting a lot of elements in the different layers.

Presentation

In the next chapters I will slowly go through the individual layers. First, let’s investigate the presentation layer:

  • Panels and lists. The basic patterns of presentation layer elements. And a straightforward implementation of a user interface independent panel. Just for the fun of it.
  • The master of detail. Introducing some typical user interface patterns, such as manage, select, search, master-detail, list-detail and the effect of those patterns on your architectural and Glue 1.0 framework requirements.
  • MVWTF. Looking at the different variations in model-view patterns, and discussing the relevance. But briefly, there’s enough said on this stuff.
  • Binding and persisting. Coupling and de-coupling your models, view-models, domain objects, whatever to and from the user interface.
  • The view-model. Sense and non-sense of view-models.
  • Validations. Things to validate and things not to validate in the user interface.

Process

Next the process layer gets a rub:

  • Why do I need a process layer? Explaining the least well-understood layer in application architectures.
  • An brief introduction to smart use cases. Still an excellent unit of work, implementation and testing for projects. This chapter will follow the lines of my presentation on this particular topic.
  • Implementing smart use cases. Introducing the task pattern, and re-iterating the manager-provider pattern. Great stuff I think! Also re-visiting the MVWTF patterns again. 
  • Unit and regression testing smart use cases. How you can write your own extensions to regular unit test framework to allow regression tests over your smart use cases.

Domain

The layer it all revolves about:

  • About being domain driven. Design, code and test, do it domain driven. Now I’m not going to repeat Eric Evans’ book here, but I do repeat (briefly) the basic concepts that derive from his work. Think of entities, repositories, factories, value objects and services.
  • Augmenting your domain. However, we are using some additional patterns in our projects that are useful patterns. Here I’ll discuss the smart reference pattern, revisit value objects, and talk about cached domain objects. I might even introduce you to search objects.
  • Business rules and validations. How do you write and where do you place your business rules, and what validations do you execute in the domain layer?
  • Independent state. Domain objects, or entities, have identity and state. This state can come from a lot of place – not all being SQL Server. So the big question here is: can I keep my domain objects independent of how we get and set its state. Well yes you can, hence the internal state patterns jumps in.

Data, services and the outside world

All the way down to the bottom, in the cellar of your application, lies the data / services layer:

  • Does the data layer really exist? Hey weren’t we using NHibernate or Entity Framework? So why do we need a data layer, and if so, what is in it?
  • Map and wrap. When querying data sources it is very likely that you will need to provide some sort of mapping from your world to the outside world. I’ll show you some simple patterns of to achieve this mapping, allowing you to wrap your domain objects around the outside world’s objects.
  • Creating queries. Again, do you really need to implement a query patterns. We do have LINQ do we? Well yes, but LINQ does not cover all your possible sources of data – a lot, but not all. So a brief introduction of the query pattern – and some implementation aspects might not be a bad idea.
  • On the way out. Somehow you will need to reach out to the outside world of your application. Virtually the outside world can be just about anything. A (set of) databases of course, but what about OData, JSON, plain or not so plain XML, restful or restless web services, Twitter, Entity Framework, NHibernate, Facebook, you name it. The gateway pattern should help you here.

Concluding

    Mildly put, this little blog post could well be the introduction for this new book – except for the thank you notes. Do you miss anything, or already agree or disagree? Want to help annotating or reviewing the manuscript in progress? Don´t hesitate to become a part of the writing process. Please comment here!

Please vote for my Microsoft Mix 2011 proposals!

From April 12-14 the next edition of Microsoft’s MIX Conference will take place in Las Vegas. I’ve sent in two proposals for the Open Call. Today heard that both proposals made it through the first cut, which means they’re open for public voting (you don’t have to be registered). It would be great if you would cast your vote for my proposals on how smart use case can drive web development, and on how frameworks can kill your projects.

300x250_Mix11_011011_US_b

Proposed talk 1: How smart use cases can drive ASP.NET web development

Use cases have been around for many years describing the requirements of software development projects. By developers, use cases are often seen as too abstract and too complex to develop code from. That is, until now.

During this interactive talk speaker Sander Hoogendoorn will demonstrate how to model, generate and build smart use cases. This great technique allows projects to model use cases at a much more pragmatic, and low-granular level, that can be implemented straightforward and directly into our applications, such as ASP.NET or Silverlight web application. The speaker will introduce both the positive impact smart use cases have on your layered software architecture, as well as the design patterns required to implement smart use cases. This talk comes with many real-life code examples.

Proposed talk 2: How frameworks can kill your projects

When it comes to .Net development, more and more frameworks enter the market. Both from Microsoft and from open source. think of ASP.NET MVC, Castle, WF, Entity Framework, Unity, Linq2SQL, ADO.NET Data Services, WCF, nHibernate, Spring.NET, CSLA, NUnit, Enterprise Library or ADF.

Once a project chooses to apply one or more frameworks, trouble begins. What if you require features that aren’t implemented in the framework? What if you decide that another framework would have been better and want to switch halfway your project? What if the author of your favorite open source framework suddenly stops developing? What if the framework contains bugs or omissions? And what if a new version of the framework is released that is implemented totally different? These and many more everyday problems will cause your project to come to a halt, or at least make you perform serious refactoring.

During this highly interactive talk Sander Hoogendoorn, chief architect of Capgemini?s agile Accelerated Delivery Platform, and member of Microsoft?s Partner Advisory Council .NET, demonstrates pragmatic architectures and patterns that will help your projects to stay away from framework issues, and how to keep code independent of framework choices. Sander will present models of layered architectures, and apply bridge patterns, managers-providers, dependency injection, descriptors, and layer super-types.

Of course, the speaker will illustrate these insightful patterns with lots of demo?s and (bad) code examples using blocks from Microsoft?s Enterprise Library, NHibernate, Log4Net, and the Entity Framework. Delegates benefit by learning how to improve the structure and quality of their software architecture and code, and how to avoid the pitfalls of applying frameworks to .Net software development.

November 12, 2010 – Microsoft TechEd Europe. How smart use cases can drive web development

[Session ARC205 at Microsoft TechEd Europe 2010 in Berlin]

Use cases have been around for many years describing the requirements of software development projects. From a developer’s point of view, use cases are often seen as too abstract and too complex to develop code from. Until now, that is.

IMAGE_004[5]

During this interactive talk, speaker Sander Hoogendoorn will demonstrate how to model, generate and build smart use cases.

image[8]_thumb[2]

This great technique allows you to model use cases at a much more pragmatic, low-granular level, enabling them to be implemented simply and directly into applications such as ASP.NET or Silverlight. Using many real-life code examples, the speaker will introduce both the positive impact that smart use cases have on your layered software architecture, as well as the design patterns required to implement them.

November 9, 2010 – Microsoft TechEd Europe. How frameworks can kill your projects.

[Session ARC203 at Microsoft TechEd Europe 2010 in Berlin]

When it comes to Microsoft .NET-connected development, more and more frameworks are entering the market, both from Microsoft and from open source. Think of ASP.NET MVC, Castle, Windows Workflow Foundation (WF), Entity Framework, Unity, Linq2SQL, ADO.NET Data Services, Windows Communication Foundation (WCF), nHibernate, Spring.NET, CSLA, NUnit, Enterprise Library, MEF or ADF.

IMAGE_004

Once you apply one or more frameworks to a project, the trouble begins. What if you require features that aren’t implemented in the framework? What if you decide that another framework would have been better and want to switch halfway through your project? What if the author of your favorite open source framework suddenly stops developing? What if the framework contains bugs or omissions? And what if a new version of the framework is released that is implemented differently?

image[8]

 

These and many more everyday problems can bring your project a halt, or at least require serious refactoring. During this highly interactive talk, Sander Hoogendoorn, chief architect of Capgemini’s agile Accelerated Delivery Platform and member of Microsoft’s Partner Advisory Council .NET, demonstrates pragmatic architectures and patterns that will help your projects avoid framework issues and to keep code independent of framework choices. Sander presents models of layered architectures, and looks at applying bridge patterns, managers-providers, dependency injection, descriptors and layer super-types, accompanied by lots of demos and (bad) code examples using blocks from Microsoft’s Enterprise Library, NHibernate, Log4Net, and the Entity Framework.

Join this interactive discussion to share your experience of improving the structure and quality of your software architecture and code, and to discuss how to avoid common pitfalls of applying frameworks to .NET software development.

Being Smart in enterprise agile

As agile is becoming more and more mainstream, organization are starting to do enterprise software development project using well-known but fairly basic lightweight agile processes.

IMAGE_089 

In many projects this has lead to surprisingly bad result, baffling the agile Certified Pokémon Trainers who are coaching these projects. The presentation below shows a number of accelerators or technique that projects can apply to tackle the challenges of enterprise agile projects.

Would be happy to hear your comments!

Simple little things. The 42 extension method anti-pattern

Extension methods are a powerful feature in .NET, and have been increasingly adopted by the developer community. Also, LINQ and other framework features rely heavily on extension methods. In fact, extension methods were originally invented to be able to implement LINQ without having to change a lot of .NET framework base classes.

Basically, an extension method is implemented as a static method on a static class. For those of you who have never seen an extension method in real life, the first parameter in the signature of an extension method might look a bit peculiar.

public static bool Execute(this Task task, params Action[] actions)
{
    foreach (var action in actions)
    {
        action.Invoke();
 
        if (!ValidationManager.IsSucceeded)
        {
            ValidationManager.Handle();
            return false;
        }
    }
 
    return true;
}

This first parameter typed with the keyword this is just syntactic sugar to be able to tell for which type the extension method is valid. In this example it is valid for any instances of the type Task, and all of its subtypes.

Like using a regular method?

Using the extension method seems exactly like using a regular method on such instances, as in the example below.

public void RemoveTag()
{
    if (this.Execute(() => Tag.Remove()))
    {
        TaskManager.Run(this, select);
    }
}

But there’s a slight difference. Since the extension method is not implemented on the type itself, it is unable to reference the private parts of the type. Moreover, the extension method can only be called only on instances of the specific type, and not statically on the type itself.

Object extension methods

So far, so good. Now, as I said earlier, the developer community has warm embraced this cool .NET feature. In fact, when you browse for extension method on the internet, there’s tons of useful – and useless – extension methods available. If you look really careful, most of these are either defined as valid for object of string.

This is where the 42 anti-pattern comes from. Sometimes it’s hard to define the best location for an extension method. In the example of the Execute() methode above we couldn’t make up our minds – should it be valid for Task or for DomainObject?

In such case I can imagine that there will be developers out there who will now define the method on object, allowing the extension method to be applied from any arbitrary object in the application, clearly not what extension methods are intended.

public static bool Execute(this object task, params Action[] actions)

42

And this is where things are starting to dive of the deep end. Not only will you end up with a whole bunch of method that have nothing to do with most objects in your application, it can get even worse when you don’t have an object in your application that I can use to call the extension method on. What to do now?

Well, what if you would just define an arbitruary object hosting the extension method. Good idea dudes! We’ll just define the int instance 42 – just to have some fun and the method on 42.

if (42.Execute(() => Product.Remove()))
 {
     TaskManager.Run(this, select);
 }

Isn’t that sweet? I’m curious to see how long it takes until we find such code.