Tuesday, November 22, 2011

DDD Kata, part 2 (Add second aggregate root to domain. Service method stage 1)

Pre-requisite: DDD Kata part 1

Completed kata example on github: DDD Kata Part 2 sample code (github)

Kata Focus
1) A second aggregate root (Inventory).
2) Business methods in each aggregate root to transfer a Product's Item from Inventory to Invoice
3) Service method, stage 1: Non-persistent, no mocks; only to verify service method created, and that it passes Item across the aggregate roots.

NOTE   The next kata (part 3) will introduce design of service orchestration through mocks, repository interfaces, and IUnitOfWork with IUnitOfWorkFactory.

The Kata
Time goal: under 30 minutes

Domain: Inventory

Note   Steps 1 through 5 work through the same concepts (object identity, read-only collections, sets) as were explored in DDD kata part 1. You may wish to build to the end of step 5 only once, and then use this as a jumping off point for the newer material in kata 2. 

1. M: New test classes for Inventory, Product and Item--start by verifying that they are instances of DomainEntityBase.
2. M: Verify that Inventory.Products is read-only collection (instance of IEnumerable<Product>).3. M: Verify that Inventory.AddProduct() increments Inventory.Products collection property.
4. M: Verify that Product.Items is read-only collection (instance of IEnumerable<Item>).
5. M: Verify that Product.AddItem() increments Product.Items collection property.

New concepts begin here.
Domain: DomainEntityBase
Changes to DomainEntityBase are necessary for proper collection add/remove behaviour on transient objects (with Id = 0).
1. M: Verify that TransientId is of type System.Guid.
2. M: Verify that TransientId has a non-empty value (i.e. not = Guid.Empty).
3. B: Verify that two instances of DomainEntityBase with 0 Id, but matching TransientId values are equal.

Domain: Inventory
4. M: Verify that Inventory.GetNewOrExistingProductBy(string productCode) returns product with matching code.

Hint   The methods in tests 7 and 8 will both call to the method created in test 6.

5. M: Verify that Inventory.StockItemBy(string productCode, int serialNumber):
  • increments count of Inventory.Products.First().Items
  • sets Product.ProductCode and Item.SerialNumber
6. M: Verify that Inventory.PullItemBy(string productCode):
  • returns Item from Product.Items
  • decrements Product.Items (i.e. the Item has been removed)
Domain: Invoice
7. M: Refactor LineItem. Change its Product property to reference Item instead. Fix and update any broken tests.
8. B: Verify that Invoice.BillItem(Item item) increments Invoice.LineItems, and that the LineItem references the billed Item.

Service layer, Stage 1
Non-persistent, just verifying the football pass of Item from one aggregate root to another.

1. M: Create new class libraries:
  • Kata.Services.Tests.Unit
  • Kata.Services
2. B: Verify that the Item pulled from inventory by productCode is the same item billed to the Invoice.

Part 2 of the kata is complete.

Continue with DDD Kata Part 3

Sunday, October 30, 2011

DDD Kata, part 1 (simple domain: Invoice and LineItem)

Kata Focus
1) Object identity and equality by Id
2) Id maintained in base class (entity object)
3) Equality on properties (value object)
4) A single aggregate root
5) Associations controlled from aggregate root (read-only, unique sets)
6) Business logic verified from the aggregate root

The kata will focus 80% on ORM mechanics (such as ORM issues of identity and equality) and 20% business requirements; tests are therefore delineated as M (for Mechanics) or B (for Business requirement).

The Kata
Time goal: under 30 minutes

A. DomainEntityBase

1. M: Verify that two instances of DomainEntityBase are equal when they have the same ID value
2. M: Verify that two instances are NOT equal when they have different ID values
3. M: Verify that two instances are NOT equal when they have 0 ID values.

B. Invoice, Money, and LineItems association
1. M: Verify that Invoice is an instance of DomainEntityBase.
2. M: Verify that LineItem is an instance of DomainEntityBase.
3. M: Verify that Money's constructor accepts Amount (decimal) and Currency (string) parameters whose values match equivalent properties.
4. M: Verify that Money.Amount and Money.Currency properties are read-only
5. M: Verify that two Moneys are equal when they have the same Amount and Currency.
6. M: Verify that Invoice has a read-only collection of LineItems.
7. M: Verify that adding a LineItem to Invoice increases its count of LineItems from 0 to 1
8. M: Verify that adding the SAME LineItem (by identifier) does not increment the set of LineItems.
9. M: Verify that the bi-directional reference (LineItem.Invoice) equals the owning Invoice.
10. B: Given an existing LineItem, when I try to add a LineItem without a ProductCode,
then I am informed that I must provide a ProductCode.

Bonus (outside the 30 minute kata window)
1. B: Given an existing LineItem with Price (type Money) of Currency CDN, when I try to add a LineItem with a USD Price, then I am informed that all LineItems must share the same Currency.
2. B: Given a set of LineItems, when I check the SubTotal for the Invoice, then the SubTotal matches Sum of the Quantity of LineItems times the Price.
3. B: Given an existing LineItem, when I try to add another LineItem with the same ProductCode,
then the original LineItem for that ProductCode has its Quantity incremented by the quantity of the added item.
4. B.Given an Invoice with LineItems, when the Currency of LineItems is USD and the SubTotal > 100M, then the Discount amount equals 5% of the SubTotal.

Continue with DDD Kata part 2

*****

Test Examples
DomainEntityBase, test 1:

[Test]
public void TwoInstance_SameIdInput_AreEqual()
{
    const int id = 1325123;
    var sut1 = new DomainEntityBase { Id = id };
    var sut2 = new DomainEntityBase { Id = id };

    Assert.AreEqual(sut1, sut2);
}

DomainEntityBase, test 2:

[Test]
public void TwoInstance_DifferentIdInput_AreNotEqual()
{
    var sut1 = new DomainEntityBase { Id = 123512 };
    var sut2 = new DomainEntityBase { Id = 64236 };

    Assert.AreNotEqual(sut1, sut2);
}

DomainEntityBase, test 3:

[Test]
public void TwoInstance_ZeroIdInput_AreNotEqual()
{
    var sut1 = new DomainEntityBase { Id = 0 };
    var sut2 = new DomainEntityBase { Id = 0 };

    Assert.AreNotEqual(sut1, sut2);
}

Invoice_Money_LineItems, test 1:

[Test]
public void Constructor_NoINputs_IsInstanceOfDomainEntityBase()
{
    var sut = new Invoice();
    Assert.IsInstanceOf(typeof(DomainEntityBase), sut);
}

Invoice_Money_LineItems, test 2:

[Test]
public void Constructor_NoINputs_IsInstanceOfDomainEntityBase()
{
    var sut = new LineItem();
    Assert.IsInstanceOf(typeof(DomainEntityBase), sut);
}

Invoice_Money_LineItems, test 3:

[Test]
public void Constructor_AmountAndCurrencyInputs_MatchGetterProperties()
{
    const decimal amount = 3.25M;
    const string currency = "CDN";
    var sut = new Money(amount, currency);

    Assert.AreEqual(amount, sut.Amount);
    Assert.AreEqual(currency, sut.Currency);
}

Invoice_Money_LineItems, test 4:

[Test]
public void Constructor_AmountAndCurrencyInputs_AreReadOnly()
{
    const decimal amount = 3.25M;
    const string currency = "CDN";
    var sut = new Money(amount, currency);

    Assert.IsFalse(sut.GetType().GetProperty("Amount").CanWrite);
    Assert.IsFalse(sut.GetType().GetProperty("Currency").CanWrite);
}

Invoice_Money_LineItems, test 5:

[Test]
public void TwoInstances_SameCurrencyAndAmountInputs_AreEqual()
{
    const decimal amount = 3.25M;
    const string currency = "CDN";
    var sut1 = new Money(amount, currency);
    var sut2 = new Money(amount, currency);

    Assert.AreEqual(sut1, sut2); // use struct for default "all-class-members" equality
}

Invoice_Money_LineItems, test 6:

[Test]
public void LineItemsProperty_Getter_IsReadOnlyCollection()
{
    var sut = new Invoice();
    Assert.IsInstanceOf(typeof(IEnumerable<LineItem>), sut.LineItems);
}

Invoice_Money_LineItems, test 7:

[Test]
public void AddLineItemsMethod_LineItemInput_IncrementLineItemsCollection()
{
    var sut = new Invoice();
    Assert.AreEqual(0, sut.LineItems.Count());

    sut.AddLineItem(new LineItem { ProductCode = "aaa"});
    Assert.AreEqual(1, sut.LineItems.Count());
}

Invoice_Money_LineItems, test 8:

[Test]
public void AddLineItemsMethod_SameLineItemTwiceInput_DoesNotIncrementLineItemsCollection()
{
    var sut = new Invoice();
    var lineItem = new LineItem { Id = 3522, ProductCode = "aaa" };

    sut.AddLineItem(lineItem);
    Assert.AreEqual(1, sut.LineItems.Count());
    sut.AddLineItem(lineItem);
    Assert.AreEqual(1, sut.LineItems.Count());
}

Invoice_Money_LineItems, test 9:

[Test]
public void AddLineItemsMethod_LineItemInput_InvoicePropertyMatchesParent()
{
    var sut = new Invoice();
    var lineItem = new LineItem { Id = 3522, ProductCode = "aaa"};

    sut.AddLineItem(lineItem);

    Assert.AreEqual(lineItem.Invoice, sut);
}

Invoice_Money_LineItems, test 10:

// Given an existing LineItem, when I try to add a LineItem without a ProductCode,
// then I am informed that I must provide a ProductCode.
[Test]
[ExpectedException(typeof(InvalidLineItemException), ExpectedMessage = "You must provide a ProductCode")]
public void AddLineItemsMethod_LineItemWithoutProductCode_ThrowsException()
{
    var sut = new Invoice();
    var lineItem = new LineItem { Id = 3522 };

    sut.AddLineItem(lineItem);
}

Sunday, July 17, 2011

Branch-Per-Feature using the Total Integration Total Isolation Principle

I posted this originally as a comment on a Google Plus thread here which has a more complete discussion on ideas (and disagreements) regarding Branch-Per-Feature and Continuous Integration.

The purpose of this entry is to focus on how a principle of Total Integration and Total Isolation shapes the approach taken to branch-per feature.

*****

In this approach, all of the following are givens:

1) releases occur on a regular basis, and development is oriented to the release schedule

2) releases coincide with merge to master

3) the release is tagged, and an empty commit immediately following the release commit is also tagged (as the start of the new cycle)

4) the existing integration ("dev") branch and the qa branch from last cycle are now re-pointed to the new start-of-cycle tag

Key point here: master is ONLY updated once per release, at the point of release--but the integration/dev and qa branches originate from (and are therefore identical to) master

The process now begins, which adheres to a "Total Integration Total Isolation" principle

1) Each developer chooses a ticket and creates a branch with that number (eg. In JIRA, tickets ABC-141, ABC-142, ABC-143, ABC-144)

2) This branch will be short lived (it won't live past the release of the ticket) although the actual commits will be preserved.

3) The developer commits frequently their branch (eg. ABC-143).

4) However, as per some of the heated discussion in this thread, the developer also merges every few hours with the integration/dev branch and checks for conflicts, compile fails, and runs all tests either locally or via the CI server's integraton/dev branch build.

5) Merge conflicts are resolved (and cached for future reuse if the DVCS permits), but outright failures requires the dev to go back to their feature branch and make the fix there, before re-attempting the merge to integration/dev branch.

6) Note that the feature branches never merge FROM the integration branch, because this would violate the isolation side of the Total Integration Total Isolation principle.

7) What about major refactorings or new archicture/scaffolding that one or more features need to share? In that case, a new ticket (eg. Dev task ABC-145) is created to hold that shared work and a branch is created (again, off of the start-of-cycle tag) to hold that major refactoring or scaffolding. The features requiring this branch change their start point/dependency from the start-of-cycle tag to this refactoring/scaffolding branch and they will retain this dependency until the end of the release.

8) Going forward, each new work is commited only to the feature branch, and each feature branch is regularly merged to integration/dev. This results in the Total Integration Total Isolation goal

9) Since every branch now originates from start-of-cycle (or from a shared refactoring/scaffolding branch that originated from start-of-cycle), QA can now safely pick and choose which features to merge onto the qa branch, and ultimately, which to release and merge to master.

10) Any features that were not released can be discarded (if rejected entirely) or rebased onto the next start-of-cycle tag (if to be resumed).

Friday, July 01, 2011

Branch-Per-Feature: Successful Transitions/Cleanup Between Sprints

.

Introduction
Branch-per-feature is the discipline of beginning every feature branch for a given sprint off exactly the same commit (typically, the first commit of the sprint). The strict enforcement of isolation between features quickly reveals the bad habits of dependencies we build between multiple features, and forces us to ask the right questions of how to keep our features independent.

Some benefits of branch-per-feature:
  • dev: proper isolation of code changes; breaking bad habits of code dependencies between branches
  • dev: embracing granularity of code changes
  • dev/QA: (almost) painless merging
  • QA: ability to assemble a release made up only of branches that are ready
(For more details, see: Branch-Per-Feature using the Total Integration Total Isolation Principle.)

Successful Transitions (Cleanup) Between Sprints
The focus of this blog post is more narrow: to define the steps involved in successful transitions between sprints/releases when using branch-per-feature to manage the release.

We've been working out the logistics for this recently at work, under the guidance of @martinaatmaa and @adymitruk. What this looks like:

Over the course of a given sprint, each feature is branched off a common commit from the start of the sprint. As each feature reaches a point of stability and completion, it is merged back into an integration branch (named something like projectname-dev). When qa is ready to test, all features to be tested are folded into a qa branch (named something like projectname-qa). Upon release, the projectname-qa branch is merged into master, and tagged as the release branch for that sprint. Now that the code has been released, the feature branches for that sprint are no longer required. They are cleaned up (deleted), although the underlying commits are kept.

Examples and screenshots illustrating this process are shown below.

To begin the next sprint, a new empty commit is created and tagged something like start-sprint2. Each feature is created off that starting commit.

Scaffolding
Early in the sprint, if it is determined that a scaffolding commit is necessary to hold some common architecture to be used by all features, a new scaffolding-only feature is created for that purpose. The scaffolding feature branch becomes the new starting point of the commit, with all features branching off that pre-requisite feature.

Feature Branch Naming
The tools we are using to achieve this are git and JIRA (with the Greenhopper plugin). For feature branch naming, we use the JIRA ticket names. For example, a project whose tickets are named PAYGATE-265, PAYGATE-286 would have corresponding git feature branch names (paygate-265, paygate-286).
Note   If a scaffolding feature is required in the sprint, the scaffolding branch will have a normal branch name (paygate-262) and all subsequent commits can indicate the dependency in their name (paygate-265-d-262, paygate-286-d-262).
Practise Scenario
To practise my skills in branch-per-feature at home, I've been using Ubuntu, rails, git, and a local install of JIRA/Greenhopper. The project used for this practise is the book Ruby on Rails 3 Tutorial. I've broken out the chapter contents into individual features as JIRA tickets. The JIRA project is broken up into very small sprints (1 weekend worth of work per sprint, probably 4-6 hours at most.)

The rest of this blog entry demonstrates branch-per-feature using this simple project, as we fold the features of sprint 1 into an integration branch, then a qa branch, and finally a release branch. We then clean up the old branches and begin work on sprint 2 features, with each one branching off the starting commit of sprint 2.

Here is a screenshot of sprint 1 in JIRA. Each ticket in this sprint has been implemented as a feature branch in git:

To complete the sprint (and its related release), I perform 3 steps:
  • merge all the tickets into the integration branch (rails-dev) and test the code
  • upon success, merge all passing features into the QA branch (rails-qa) and test the code
  • upon success, merge rails-qa into master and tag it as the release. 
Here's the gitk view:

With the sprint completed, its time to go back to JIRA (with Greenhopper plugin) to verify that everything in sprint 1 is closed, and that remaining story points are at 0, followed by setting up and prioritizing sprint 2:


Now that sprint 2 is prepared, its time for the dev team to begin coding. I switch to the JIRA/Greenhopper task board view, and drag my first ticket LRNRAILS-15 to In Progress:

To begin work on the dev tickets, we need to first set up the new sprint 2 in git. I begin by creating an empty commit off the release/master branch. To do this, I checkout the tag release-lrnrails-sprint1 (or master branch). To create the new commit of an empty branch:

git commit --allow-empty


I then tag it with a name such as start-lrnrails-sprint2.

git tag start-lrnrails-sprint2


Since this is the commit that all features will originate from, I will also move my integration (rails-dev) and QA branches (rails-qa) to this commit (by checking out eadch branch and then using git reset --hard start-lrnrais-sprint2 to point them at the starting commit).


Finally, we need to CLEAN UP (remove) all of the sprint 1 feature branches as they are no longer needed. At work we relied on @adymitruk's bash scripts to construct the delete commands that clear out both the local and remote branches. I tried this with my project at home, and got it to work successfully.

For example, given that I want to delete branch jira-lrnrails-5, my local and remote commands would be:

git branch  -D jira-lrnrails-5
git push origin :jira-lrnrails-5

To achieve this for all branch-per-features which were merged into the release, I checkout the release-lrnrails-sprint1 tag (or master branch), and then run the following commands, first as preview (using echo to verify the commands):

git branch --merged | grep lrnrails -i | xargs -i{} echo git branch -D {}



and then the actual execution of the commands:

git branch --merged | grep lrnrails -i | xargs -i{} git branch -D {}


Then, the same for the remote branches:

git branch -r --merged | grep lrnrails -i | cut -d '/' -f 2 | xargs -i{} echo git push origin :{}
git branch -r --merged | grep lrnrails -i | cut -d '/' -f 2 | xargs -i{} git push origin :{}



Now, with all of the branch-per-features deleted for sprint1, the view is much cleaner in gitk:

Now I  am ready to begin work on sprint 2. I checkout the starting point commit for this sprint:

git checkout start-lrnrails-sprint2


and then create my feature branch off that starting commit:
git checkout -b jira-lrnrails-15


I then proceed to write the code for this feature. At a certain point, I will do one-or-more commits for this feature branch:git add . -A
git commit -m "LRNRAILS-15 Adding variables to the views"


With the feature branch commited, I am ready to test my integration branch for the first time. I check out the integration branch (myprojectname-dev) and then do a git merge --no-ff against the new feature branch:

git merge --no-ff jira-lrnrails-15


This gives me my first integration branch merge of sprint 2:


From here, the second (and subsequent) sprints can move forward using branch-per-feature to properly isolate code changes, and to enable QA to assemble release packages based on a specifically chosen subset of verified features.

Saturday, March 19, 2011

Programming in sprint cycles (currently sprint 9)

This post is meant to be a bit more observational than my usual how-to posts. It describes what my first experiencing of programming in agile sprints has been like.


The sprints are set up on a weekly basis, starting on Thursday mornings with a demo to the stakeholders of the completed user stories of the just-completed sprint. Each demo is guided from a list of (JIRA-based) tickets stored in a spreadsheet. Read out the ticket name, go over the acceptance criteria, hide the spreadsheet, and demo the completed feature. The demos are recorded in Camtasia the day before, and stored online for ease of access by all members of the team for later reference (eg. an additional stakeholder added, or a team member back from 2 weeks vacation.)

With the demo completed, a sprint retrospective is held, guided by our team lead / SCRUM person. We go into more or less detail, depending on the week. (I've found this process to be amazingly clarifying and effective.) We then launch JIRA, go into the planning view, add any additional stories/tasks/bugs that have become evident, assign any unassigned tickets, and place them in likely order of completion. We then go through each ticket and give it a story point rating. This rating has been much easier to arrive at with each subsequent sprint. We look at the total story points, knowing what our average velocity has become per sprint, and either push tickets back to the later sprint, or perhaps bring tickets forward into the current sprint if we are a bit short.

By this point it is typically noon, so we grab some lunch. After lunch I go to my desk, launch JIRA, flip into task board view, and look at my backlog. I drag over (typically the top) item. Most of the time, I try and keep only one item in the active development swimlane (although by the end of a busy day (several smaller story-point tickets may have traversed from backlog to development to tech review).

I'm now ready to start, so I launch git bash, change to the git repo containing the area where I need to work (typically web applications, with a submodule to shared libraries). I do a git fetch to get the latest, checkout the dev branch, and do a git merge --ff-only origin/dev if necessary to catch up to latest. I think create a local branch on that commit, typically with the JIRA ticket number in the branch name, something like prd1234addMissingNullCheckGuardCondition (I've always liked long descriptive branch names or for that matter, method names in code, since tab in git or Intellisense in VS will be happy to save the typing effort for me later).

At this point, I try and gain an understanding of what's needed. Before long, I'll be creating an NUnit test. The test may be in a Nnnn.Tests.Unit project or Nnnn.Tests.Integration project, depending on the legacy code situation and the ability (or inability) to substitute dependencies. The general rule of thumb at the moment is that if it is new functionality altogether, or if it is abstracting/creating utility or helper classes that work with the legacy code, then unit tests will be possible. For new functionality, dependencies are DI injected and tested with mocks or just simple fake implementation classes.

When the work is completed, I do git fetch again, fast-forward the dev branch if new work has been added, swtich to my local branch, rebase it onto the dev branch, switch to dev branch, merge in my local branch, check the results in gitk --all GUI view, and if everything looks clean, push it to origin. I do this in the submodule / shared libraries first, and then again at the outer level. This push to origin is picked up on by the TeamCity integration server, and for the current project that typically means I want to wait for TeamCity to create the artifact (a combination of pages and xcopy-deploy style DLLs whose structure I have setup and verified on TeamCity awhile back). When TeamCity, compiles, passes all tests, and generates the artifact successfully, I save the artifact zip file to the web server, unzip it, move it to the deploy directory, update the permissions, and repoint the IIS home directory for that project to the new artifact.

With that complete, I drag the ticket from the development swimlane to the tech review swimlane, alert QA that it is ready, typically verbally, often supplemented with an explanation either in JIRA or just in email, and look to my backlog for the next ticket.

This is of course interrupted constantly by emails, discussions, whiteboard sketch sessions, morning standup, and afternoon coffee runs, but in the midst of daily chaos, the ongoing process of the tickets moving across the swimlanes gives a steady rhythm to the week.

We're in sprint 9 now, and probably one more sprint will finish this project. Another project is in the works. This is a most interesting, satisfying, and fascinating process.