Friday, May 09, 2008 #

Writing unreliable software

This had surprised me, to say the least.  I run into a bug during stress testing for SvnBridge, after a while, it would simply get stuck.

I am not the best in figuring out exactly what got an application stuck, but I generally manage to put some effort before I call the big guns. This time, I had managed to get a working theory, and prove that that the symptoms that I am seeing are consistent with my theory. I decided to dig into it a bit, and came up with interesting results.

The jury is still out with regards to whatever that was the real reason for the issue that SvnBridge has, but I went to some trouble to try research my theory. Along the way, I came up with some interesting conclusions. Just a reminder, I recently read Release It! and I consider this a very influential book. One of the things that kept coming up in the book how a chain reaction can take down an application. A single server stop responding, and the other just crumble. The examples that were brought up were of two kinds: a flawed implementation of a connection pool and a mismatch in the various capacities of the systems.

In SvBridge, there is a location where I am doing something similar to this code:

revision = webService.GetLatestVersion()

items = webService.GetItems(revision, downloadPath)
items.ForEach { item | item.BeginDownload(webService) }

comment = webService.GetLog(revision)items.WaitForAllToFinishDownloading()

SendToClient( Revision: revision, Comment: comment)

for item in items:
	item.WaitUntilLoaded()
	SendToClient( item.Data )

This isn't the exact code, but is is a good, and simple, representation of what is going on. This code can lead, under a set of special circumstances, to a hung server.

The important thing to understand here is that this code is using BeginDownload to perform an async invocation over HTTP. Another important data point is that we tend to hit the same physical server for a lot of our work.

Take a look at what is actually happening...

image

It is very important to observe that GetLog is actually never executed. The .NET framework is allowing (by default) only 2 connections to a server. GetLog will only be executed when there is a free slot, and the async requests are ahead of it in the queue. (Actually, I haven't verified the exact sequence in which this would be executed).

Until the async requests complete, we are stuck. This is important because it is not limited to the current request. This means that one request can block another, and since a single checkout request can cause a few thousands sub requests to the backend server, a single user can take down the system for a significant amount of time. (What I am actually seeing is a bit different, it looks like the async request never returns from the server under high load, but never mind that).

There are solutions for all of that, connection groups and overriding the number of allowed connections per server are just few of them.

I remember reading the Release It book and being thankful that so much of that seems to be focused on Java (thankfully, I have never had to write my own connection pool, which seems to be a pastime in the Java land).

What caused this post was actually my defensive approach failing with spectacular results. I started by defensively specifying Timeout on my requests. It would kill the request, but it would also keep the server alive. Unfortunately, Timeout has no affect on async requests. I found this very surprising, even though it is documented, I consider this a bug. Even worse, the actual example in BeginGetRequest is for fixing this issue, to support Timeout in async requests.

Leaving aside my own annoyance for hitting this tripwire, it brought to mind sharply that we should be very aware of the things that we do, and how they affect the longevity and scalability of our solutions. I spent most of the beginning of this week and the end of last week doing just that, throwing huge amounts of code and requests at SvnBridge. This was the most interesting problem that it had, and it was very interesting to see how we will solve that.

posted @ Friday, May 09, 2008 10:29 AM | Feedback (7)

Tuesday, May 06, 2008 #

Choose the appropriate medium

I recently started using Tweet, which also means that I mostly watch conversations go past. It has made me feel even more strongly about selecting the appropriate medium for discussion.

You cannot have a meaningful discussion in Twitter or IM, the conventions and limitations of the platform. Email is a better medium to expression complex concepts, but voice or video are far better methods of communication. Obviously, nothing can supercede the quality of discussion face to face, especially in open space format.

The other side of the coin is the cost of this interaction.

  • IM / Twitter have very little cost, and almost zero expectation. I can send you an "r u thr?" msg without being disturbed by that.
  • Doing the same in email, however, would be unacceptable, because I have different expectations from email.
  • Escalating to voice or video is far more costly. Now you have tied people in time, which tends to be very hard. I just had a discussion with someone that is 8 hours away from me. Finding the right time to schedule the conversation was... interesting.
  • Face to face ties use in both space and time
  • Etc...

So you have to choose the appropriate medium for the message you are trying to send.

Sending the message using inappropriate medium tends to cause difficulties.

posted @ Tuesday, May 06, 2008 12:43 PM | Feedback (11)

Pluggable Domain Model

Rhino Security is a good example of a pluggable domain model, in which we can plug some functionality into different and varied domains.

Here is an interesting demo to show how you can use it.

public static void DemoUsingCustomerCareModule<TCustomer>(string schema)
    where TCustomer : ICustomer, new()
{
    Configuration cfg = new Configuration()
        .Configure("hibernate.cfg.xml")
        .AddAssembly(typeof(Lead).Assembly)
        .AddAssembly(typeof(TCustomer).Assembly);

    cfg.MapManyToOne<ICustomer, TCustomer>();
    cfg.SetSchema(schema);

    new SchemaExport(cfg).Execute(true, true, false, true);

    ISessionFactory factory = cfg.BuildSessionFactory();

    object accountCustomerId;
    using (var session = factory.OpenSession())
    using (var tx = session.BeginTransaction())
    {
        var customer = new TCustomer { Name = "ayende"};
        session.Save(customer);
        var customerCareService = new CustomerCareService(session);
        customerCareService.GenerateLeadFor(customer, "phone call");
        customerCareService.GenerateLeadFor(customer, "email ");
        tx.Commit();

        accountCustomerId = session.GetIdentifier(customer);
    }

    using (var session = factory.OpenSession())
    using (var tx = session.BeginTransaction())
    {
        var customer = session.Get<TCustomer>(accountCustomerId);
        var customerCareService = new CustomerCareService(session);

        Console.WriteLine("Leads for: " + customer.Name);
        foreach (var lead in customerCareService.GetLeadsFor(customer))
        {
            Console.WriteLine("\t" + lead.Note);
        }

        tx.Commit();
    }
}

This can be used with:

DemoUsingCustomerCareModule<AccountingCustomer>("Accounting");

DemoUsingCustomerCareModule<HelpDeskCustomer>("HelpDesk");

posted @ Tuesday, May 06, 2008 11:48 AM | Feedback (3)

Course: Building Domain Specific Languages in Boo

You can register here for a two days course in building DSL with Boo.

It is going to take place two weeks from today, in Austin. (19 - 20 May)

I know that this is short notice, but it wasn't something that was planned well in advance. It came out of the ALT.Net conference.

Topics:

  • Creating Domain Specific Languages
  • The Boo Language
  • Flexible compiler and malleable language
  • Creating applications with embedded DSL
  • Management, tracing and debugging
  • Tooling support
  • Testing and maintainability concerns

There are ten seats open for that.

I hope we would have fun.

I would also like to thank Jeffrey Palermo and Headspring for hosting the course.

posted @ Tuesday, May 06, 2008 4:33 AM | Feedback (5)

Monday, May 05, 2008 #

How do you track that?

I have an interesting problem with SvnBridge.

After around 5000 full revision request (a set of requests that can occur), the application get hung making a web service call to TFS. This comes after making quite a few calls to TFS, and is generally fairly easily reproducible. The actual call being made is not an expensive one (nor is it the same call). TFS is responsive during that time, so it is not its fault.

It looks very much like I am hitting the 2 concurrent HTTP requests, except that all requests are serialized, and there is no multi threaded work involved.

I have been unable to reproduce this under a profiler or debugger...

Thoughts?

posted @ Monday, May 05, 2008 11:09 AM | Feedback (9)

Actively enforce your conventions

Glenn posted about a test I wrote PrismShouldNotReferenceUnity, in which I codified an assumption that the team made.

This is something that I try to do whenever I decide that I will have some sort of convention. If at all possible, I will try to make sure that the compiler will break if you don't follow the convention, but that is often just not possible, therefor, tests and active enforcement of the system convention fill that place.

Wait a minute, I haven't even defined what a convention is! By convention, I means things like:

  • All services should have a Dispose() method - add a Dispose abstract method to AbstractService
  • All messages should be serializable - create a test that scan the message assemblies and check for non serializable messages.
  • You may only call the database twice per web request - create an http module that will throw an exception if you call it more than that
  • A request should take less than 100 milliseconds - add an interceptor that would fail if this is not the case.
  • The interface assembly may not contain logic - add a test that would fail if it find a class with a method on the interface assembly.

All of those are ways to increase the feedback speed. This is especially true if there is some extra step that you need to perform, or you want to draw a line in the sand, from which you will not deviate.

Actively enforced conventions keep you honest.


posted @ Monday, May 05, 2008 10:51 AM | Feedback (11)

Sunday, May 04, 2008 #

Matching SVK revision numbers to the SVN revision number

I am using SVK as a way to learn Distributed SCM, and it has been fine so far, except for one big issue. I am trying to apply SVN patches against SVK repositories. Actually, I setup SVK so I work against a local mirror, so the workflow is exactly the same, but the revision numbers are different. Trying to apply a patch just doesn't work because of that.

You can go into the patch and edit the root revision, so it is not hard to make this work, except...

SVK doesn't give me any mapping between the local revision and the source revisions (maybe it does, and someone will tell me). This means that in order to find the right version I have to do some annoying digging.

Here is a small program to solve the problem. Usage: svkrev [repository path] [source revision to look for]

Example:

C:\Users\Administrator>svkrev //mirror/nhcontrib 195
exec: svk log --xml //mirror/nhcontrib
exec: svk propget svm:headrev //mirror/nhcontrib --revprop -r 10298
exec: svk propget svm:headrev //mirror/nhcontrib --revprop -r 10260
:=349
exec: svk propget svm:headrev //mirror/nhcontrib --revprop -r 10092
:=195
Found: 10092

By the way, can you find the huge perf boost here?

class Program
{
    static void Main(string[] args)
    {
        string path = args[0]; // "//mirror/nhcontrib"
        var revisionToSearchFor = int.Parse(args[1]); // 195
        string logAsXml = ExecuteProcessAndReturnOutput("log --xml "+ path);
        var doc = new XmlDocument();
        doc.LoadXml(logAsXml);
        var list = new ArrayList();
        foreach (var node in doc.SelectNodes("/log/logentry/@revision"))
        {
            list.Add(node);
        }
        for (int i = 0; i < list.Count;i++ )
        {
            XmlNode node = (XmlNode)list[i];
            int revision = int.Parse(node.Value);
            string output = ExecuteProcessAndReturnOutput("propget svm:headrev " + path + " --revprop -r " + revision);
            if (string.IsNullOrEmpty(output.Trim()) == false)
            {
                var currentRevision = int.Parse(output.Split(':')[1].Trim());
                Console.WriteLine(":=" + currentRevision);
                if (currentRevision == revisionToSearchFor)
                {
                    Console.WriteLine("Found: " + revision);
                    return;
                }
                i += (currentRevision - revisionToSearchFor -1);
            }
        }
    }

    public static string ExecuteProcessAndReturnOutput(string args)
    {
        var output = new StringBuilder();
        string err = null;
        var psi = new ProcessStartInfo
                      {
                          FileName = @"c:\Program Files\svk\svk.bat",
                          Arguments = args,
                          RedirectStandardOutput = true,
                          RedirectStandardError = true,
                          CreateNoWindow = true,
                          UseShellExecute = false
                      };
        Console.WriteLine("exec: svk "+ args);
        Process svk = Process.Start(psi);
        ThreadPool.QueueUserWorkItem(delegate
        {
            err = svk.StandardError.ReadToEnd();
        });
        ThreadPool.QueueUserWorkItem(delegate
        {
            string line;
            while ((line = svk.StandardOutput.ReadLine()) != null)
            {
                Debug.WriteLine(line);
                output.AppendLine(line);
            }
        });
        svk.WaitForExit();
        if (string.IsNullOrEmpty(err) == false)
            throw new InvalidOperationException(err);
        svk.WaitForExit();
        Thread.Sleep(500);
        return output.ToString();
    }
}

posted @ Sunday, May 04, 2008 8:21 PM | Feedback (0)

Rhino Mocks new feature: Persistent Mock Repository

Here is something that most people just don't think about, but it hits some people at the high end. Rhino Mocks is extremely efficient mocking framework, for just about any scenario. However, when it comes the time to mock large amount of interfaces (where large is in the hundreds to thousands), or if you have large interfaces (with dozens or hundreds of methods), it is costly to create the mocks. Rhino Mocks does some caching internally, but it is noticeable issue to some people.

We are talking about an extra second or two for even most pathological cases here, so in general it wouldn't warrant anything special from the library, except that there is a twist to that. Under the debugger, the amount of time spent actually generating the mock can go up by several orders of magnitudes. To add insult to injury, Visual Studio 2005 has several bugs in the debugger that will cause it to crush & burn under several such scenarios.

Karl Lew has been kind enough to not only provide a patch for Rhino Mocks to help this problem, but also document it.

Thanks, and sorry for taking so long to get to it.

posted @ Sunday, May 04, 2008 2:02 AM | Feedback (0)

Solving the impendence mismatch between Hierarchical Data and XML

I was very impressed when I saw how Subversion handles the complexity of having data of hierarchical nature that needs to be serialized to XML. Check this out.

<?xml version="1.0" encoding="utf-8"?>
<S:editor-report xmlns:S="svn:">
  <S:target-revision rev="11"/>
  <S:open-root rev="-1"/>
  <S:open-directory name="tags" rev="-1"/>
  <S:add-directory name="tags/asd"/>
  <S:close-directory/>
  <S:close-directory/>
  <S:close-directory/>
</S:editor-report>

And here is another one:

<?xml version="1.0" encoding="utf-8"?>
<S:editor-report xmlns:S="svn:">
  <S:target-revision rev="15"/>
  <S:open-root rev="-1"/>
  <S:open-directory name="trunk" rev="-1"/>
  <S:open-file name="trunk/a.txt" rev="-1"/>
  <S:apply-textdelta checksum="eabc96676e7defda414a1eed33bdfb09">
    U1ZOAAAQEwETk2FzZDENCjINCjMNCjQNCjUNCjY=
  </S:apply-textdelta>
  <S:close-file checksum="c6301e5dad1330a7b9bd5491702c801b"/>
  <S:close-directory/>
  <S:close-directory/>
</S:editor-report>

I was, as they say incredibly happy with this Work Time Fun.

posted @ Sunday, May 04, 2008 1:38 AM | Feedback (1)

Saturday, May 03, 2008 #

Go with High End Solutions

About a year and a half ago, I start an exciting new project (there is a demo of the actual project here). The actual application is fairly complex, and has some it gave me the chance to explore some very interesting ideas. Rhino Security is a generalization of the security scheme used in this project, and it is pretty much the driving force for Rhino Igloo. But that is not what I want to talk about.

What I do want to talk about is the infrastructure that we used for the project. We used IoC, OR/M, AoP, MVC and many other buzz worthy TLD. It was the first time that I had the chance in implementing real high end complexity reduction techniques. I left the team 10 months ago. In the meantime, the application was under active development, got a few new team members and had two major releases.

I am really proud of that system.

A few weeks ago I got a phone call from the current team lead, asking me about the windsor.boo file that is sitting there. The last time anyone touched it was shortly after I left, after which, it just... existed. I had the chance to do a code review on the new stuff that the team developed, about three months ago. I couldn't find any real difference between the code develop before and after I left.

Anyway, I had to spend 15 minutes on the phone, explaining the process that was going on there. Before I left (and during the time I was the team lead), I made sure that I passed on all the knowledge that I had about the system, the design decisions and the overall picture. However, there was a period of nearly three months in which I forgot that we even had this infrastructure, because we hadn't have to deal with it for that time period. After I left...

  • 9 months.
  • 2 major releases.
  • Zero issues with the infrastructure.

I asked the team lead what she thinks about that. Since it is her project now, and if she thinks that it was the right decision to make. She love the infrastructure, and wouldn't hear about using a lower end solution. Most of what we did was actually going over the file and explaining historical decisions, for that matter.

As an additional data point, I was able to look at a piece of code I have last seen over a year ago and figure out not only what it does, but the how and why of it with no ramp up time.

I consider this a success.

posted @ Saturday, May 03, 2008 2:00 PM | Feedback (7)

How to test this?

I have an interesting problem. I am currently working on adding sync support to SvnBridge. This mostly involves tracking down what SVN does and duplicating it myself. There isn't that many new code (I had to add a class and a method). I have nothing that I can _unit_ test. Oh, I could probably craft something, but I am reluctant to mock the world when what I want is to test the actual integration between the SVN client and the TFS server.

The problem is that I have no real way of testing this. Why do I mean by that? syncing is an operation that works over the entire repository, from start to finish. I can't perform an integration test, because that would take too long in most scenarios. I would need to setup a whole new TFS server per test. Not really a good solution, no matter how you turn the dice. And the other problem is that I need a rich set of situations to actually test this.

Right now I am driving that by going against the production CodePlex servers, and trying to see if I can get all the information from there. This is incredibly valuable, because it gives me access to a lot of source control practices that are out there, and expose a lot of false assumptions. But I can't write tests for those, because they are too big a scenario.

I can probably set an integration test that would execute against the production servers (they are publicly exposed, after all, so no issue there), but I am... less than thrill about having a single test that can potentially run for hours or days.

Right now I think that I am leaning toward partial fake of both the client and the server. That is, create a set of input values which, while not being the real world values, would still test that logic.

Considering that the actual interaction we are talking about take place over many requests, this is tricky, but possible.

Suggestions?

posted @ Saturday, May 03, 2008 3:45 AM | Feedback (4)

Friday, May 02, 2008 #

Avoid retrofitting unit tests

I was asked this a few days ago, should we spend time creating proper unit tests for our code?

The team in question already have a set of integration tests, but very few tests that qualify as unit tests.

My response was rambling and long, but it can be put down to the following statement: TDD is a design technique, not a testing technique. TDD, and especially test first, have the nice side affect of leaving tests as part of the process, which can be incredibly helpful when you are working with the code. But retrofitting tests? That tends to be a waste of time.

Writing a unit test before touching the code is absolutely the way to go, but going and adding unit tests, as a task of its own? I don't see the value in that.

If you have integration tests there, that tends to be good enough, and you will write unit tests when you change the code, so eventually you'll have enough unit tests ( eventually you will have enough unit tests on the hot spots ).

posted @ Friday, May 02, 2008 1:21 PM | Feedback (14)

Thursday, May 01, 2008 #

Playing with numbers

For some reason, it looks like it is a good day to be impressed by numbers, here it the most interesting one...

image001.png

And here is the reason you don't want to use the file system as your database:


image001-2.png

posted @ Thursday, May 01, 2008 11:59 PM | Feedback (3)

Dynamic Mapping with NHibernate

A while ago I posted how to handle dynamic mapping with Active Record, it was incredibly easy to do, because Active Record has a lot of smarts internally, and output the XML, on top of which NHibernate adds quite a bit of convention over configuration as well. Doing the same using NHibernate directly is possible, but a bit long winded. Here is the sample code, which link all the Employee properties to the correct entity:

Configuration cfg = new Configuration()
    .AddAssembly(typeof (Employee).Assembly)
    .AddAssembly(typeof(ScheduledTask).Assembly);
Mappings mappings = cfg.CreateMappings();
foreach (PersistentClass persistentClass in mappings.Classes)
{
    if (persistentClass.MappedClass.GetProperty("Employee") == null)
        continue;
    Property prop = new Property();
    PersistentClass employeeClass = cfg.GetClassMapping(typeof (Employee));
    Table table = employeeClass.Table;
    ManyToOne value = new ManyToOne(table);
    value.ReferencedEntityName = typeof (Employee).FullName;
    Column column = new Column("Employee");
    value.AddColumn(column);
    prop.Value = value;
    prop.Name = "Employee";
    prop.PersistentClass = employeeClass;
    persistentClass.AddProperty(prop);
    persistentClass.Table.AddColumn(column);
    persistentClass.Table.CreateForeignKey("FK_EmployeeTo" + persistentClass.MappedClass.Name,
                                           new Column[] {column,}, typeof (Employee).FullName);
}
cfg.BuildSessionFactory();
new SchemaExport(cfg).Execute(true, true, false, true);

As you can see, there is a lot that needs to be done, we have to tell NHibernate a lot of things it would generally be able to figure out on its own. We can shove this to an extension method and get really nice syntax:

public static void MapManyToOne<TEntityInterface, TEntity>(this Configuration cfg)
{
    Mappings mappings = cfg.CreateMappings();
    foreach (PersistentClass persistentClass in mappings.Classes)
    {
        var propertyNames = new List<string>();
        foreach (PropertyInfo property in persistentClass.MappedClass.GetProperties())
        {
            if (property.PropertyType == typeof (TEntityInterface))
            {
                propertyNames.Add(property.Name);
            }
        }
        if (propertyNames.Count == 0)
            continue; 

        var prop = new Property();
        PersistentClass targetClass = cfg.GetClassMapping(typeof (TEntity)); 

        foreach (string propertyName in propertyNames)
        {
            Table table = targetClass.Table;
            var value = new ManyToOne(table);
            value.ReferencedEntityName = typeof (TEntity).FullName;
            var column = new Column(propertyName);
            value.AddColumn(column);
            prop.Value = value;
            prop.Name = propertyName;
            prop.PersistentClass = targetClass;
            persistentClass.AddProperty(prop);
            persistentClass.Table.AddColumn(column);
            string fkName = string.Format("FK_{0}To{1}", propertyName, persistentClass.MappedClass.Name);
            persistentClass.Table.CreateForeignKey(fkName,
                                                   new[] {column,}, typeof (TEntity).FullName);
        }
    }
}

Now we can use this with the following syntax:

cfg.MapManyToOne<IEmployee, Employee>();

Which is much nicer.

posted @ Thursday, May 01, 2008 1:21 PM | Feedback (6)

The single line bug fix

It is amazing how much time you can hunt for the exact cause of a bug. In this case, it took me almost two days (intersperse with other work, however) to track down and find the issue.

Remember, there is no reason to use ASCII, ever. I actually run a blame on the code (a new feature for SvnBridge!) to find out who wrote it. And then I sent a nasty email about it to /dev/null, just to clear my mind.

image

posted @ Thursday, May 01, 2008 11:17 AM | Feedback (1)

Wednesday, April 30, 2008 #

Extreme Patterns Video

I was asked a few times about recording a course, so I think that a few people would be happy to know that Glenn Block has posted a discussions that we had about a month ago.

You can find it here

posted @ Wednesday, April 30, 2008 5:08 AM | Feedback (4)

I don't like distributed source control

Let us see how much of a furor that will cause :-)

The reasons that I don't like distributed source control systems have nothing to do with technical reasons, and everything to do with how they are used and promoted.

The main reason that I don't like them is that they encourage isolated work. I don't want anyone in my project to just go off and work on their own for a few weeks, then come back with a huge changeset that now needs to be merged. The way DVCS are promoted, this is a core scenarios, "no one have to see your mistakes".

I completely disagree. I want to see everyone's mistake, and I want them to see mine. There is little to learn from successes, and much to learn from failures. Far worse, I want to be able to peak into other people work, so I can give my input on it, even if it is just "wow, nice", or "yuck, sucks!"

The main advantage of DVCS is speed, trying to browse the repository when you have the entire thing on your HD is lightning fast, compared to doing the same with a remote repository. I tend to use SVK now, but I use it strictly as a local cache, and nothing more.

And that is why I don't like DVCS, I don't want people to work in isolation.

posted @ Wednesday, April 30, 2008 4:51 AM | Feedback (16)

DevTeach Toronto

In about two weeks, I'll be speaking at DevTeach again. The last two times were enough to recharge me for months afterward, and I am looking forward to it.

I am giving several talks there.

Advance IoC - The use of advance means that I get to assume that I don't need to deal with intro stuff, so I am going to try to cram anything from generic specialization to aspect orientation to hierarchical containers and infrastructure ignorant applications. Success metric: people coming out of the talk saying "my head hurts".

OR/M += 2 - OR/M is no longer on the edge, it is a mainstream technology. That said, there isn't much knowledge out there how to take advantage of the non obvious advantages of using an OR/M. I am going to cover multi tenancy, adaptive and partial domain models, approaches for scaling, application design and architecture with OR/M and a bunch more.

DSL - This is a favorite topic of mine, this is actually an introductory talk, covering the range from "why do we even need this" to "how do we build them?"

Rapid (maintainable) web development with MonoRail - I gave that talk a few times already, but I think it is a time to give it a bit of a facelift, and try to do something a bit more impressive. So this is not going to be just "yet another intro to IoC", and it is going to dig deeper into more interesting scenarios.

I am also going to talk in a DotNetRocks panel, about the future of software development. The other parties in the panel are Scott (the Blogless) Bellware and Ted Newart.

Those are my talks, there are a lot of other good ones, and of course, the real reason that DevTeach is so much fun is the interactions with the people

posted @ Wednesday, April 30, 2008 4:22 AM | Feedback (7)

Thoughts about building your own source control

Let me start by stating that you really don't want to do that. This is not something that you want to do, period.

Now that we are over that, I had the chance lately to go fairly deeply into SCM and how they are implemented from two fairly different perspectives. This is a randomly collected set of observations about SCM systems. As usual, the order is arbitrary, and no attempt was made to make any coherent idea out of this.

  • It is all about the client. The client in an SCM system has significant responsibilities. It is in charge of reporting the client state, managing all the errors hat the user can cause, and shoulder a lot of the burden.
  • It is all about the protocol. Anyone who designs a SCM system should be given a lousy DSL line with disconnects every 15 minutes. Oh, and they should also have to work on a plane a lot.
  • On the wire, it is all so simple. It is really surprising to see how the SCM complexity is really just a lot of tiny, easy to handle, details.
  • The devil is in the details, though.
  • Complexities on the server side:
    • Space management - do you save the diff or the whole file?
    • If just the diff, how do you construct an arbitrary version
    • Keeping history around for branches and copies
    • Cheap copies

  • Complexities on the client side:
    • Do you have one version on the client, per the working copy?
    • Do you have multiple versions, one per each file?
    • Handling inconsistencies between server version, working copy version and original version.

  • What do you optimize for? Bandwidth? Roundtrips?
    • I know of one SCM product who is lousy optimizer for both

  • Distributed SCM can be handled on top of centralized SCM.
  • It is not hard at all, except for all the details.
  • Don't write your own SCM.
  • Trust matters, and you really don't want to be in the situation where you don't trust your SCM.
  • Remember that SCM is temporal, you can go backward in time, and even sideway, to a branch.
  • There are only three types of operations in SCM:
    • Generate a change set between two paths at two versions
    • Apply a diff to a path, generating a new version
    • Reporting (logs, mostly, and outputting various formats of a changeset)
Overall, it is very simple endeavor. It gets complex when you start talking beyond the wire protocol. As a simple example, how much does it cost you to branch? How much does it cost you to find out if there has been any changes to the working copy?
The other major issue is: How do you ensure that it is reliable?
Now, let me repeat myself, do not write your own source control!

posted @ Wednesday, April 30, 2008 1:47 AM | Feedback (11)

Thursday, April 24, 2008 #

Customer experience to treasure

I just finished talking with Apple Store's support. Due to a misunderstanding on my part, I ended up buying two iWorks licenses, instead of one. I called the store, got a human in less than a minute, explained the issue, waited a bit while she fixed things up so I get a refund for the extra license, done.

I wish it was all like this.

posted @ Thursday, April 24, 2008 6:10 PM | Feedback (5)

Critical Mass

When a software project is started, there is a lot of new functionality to be written. Most of the time you are dealing with new code. After a while, if it is done right, you have a solid foundation that you can use to keep building the application.

Whenever I recognize this moment, it feels me with deep sense of satisfaction, this is how it should be. Rhino Mocks has reached that point some years ago, and I noticed that SvnBridge has crossed that point as well recently.

As telltales for this, I use the amount of effort it takes to build a new feature into the project. Take a look wt what was required to add svn sync support to SvnBridge, or the AAA support for Rhino Mocks.

posted @ Thursday, April 24, 2008 6:36 AM | Feedback (2)

Source control practices to be abolished: The Large Checkin

I have just had to make a modification to SvnBridge because someone decided to checkin a change set that included 1,695 changes..

Even ignoring the issue of "your checkin broke my source control" mentality that I have now, this is bad from other perspective.

How do you review such a change? How do you even know what is going on there? In changes of this size, you aren't making a single change, you are making a lot of changes. Now you can't mix & match them either.

Prefer small checkins, they are easier to deal with.

posted @ Thursday, April 24, 2008 3:25 AM | Feedback (13)

SvnBridge 2.0 Released!

Took a lot of work, and several months, but it is here:

Download Page

posted @ Thursday, April 24, 2008 2:28 AM | Feedback (8)

Monday, April 21, 2008 #

On API Design

Krzysztof points out that Rhino Mocks has 26(!) methods to create various mocks.

I was very surprised to hear that, so I took a look, and he is right. The problem is how you think about those. Those 26 methods are only applicable when you count each overload as a separate method. Without counting the overloads, we have 10 methods. Then we have to consider options. In Rhino Mocks, you tend to have the following options: Create mock, create mock with remoting, create mock from multiple types.

That works to reduce the amount of options that we have down to about 4 different types of mocks that Rhino Mocks can generate. And you know what. Almost all the time, you don't need to use them. You only have to use DynamicMock and that is it.

Krzysztof suggestion would consolidate all the options to a far smaller surface area, but it would have several major issues:

  • Break backward compatibility - I am wiling to do that, but not without due consideration.
  • Break the documentation - See previous point.
  • Force the user to deal with all the options explicitly - "In your face!" API design, from my perspective. That would lead people to create utility methods over it, which gets us right back to all the method options.

posted @ Monday, April 21, 2008 7:34 PM | Feedback (7)

DSL Article on InfoQ

image_boo.jpg

My DSL article has been published on InfoQ!

You can get it here

posted @ Monday, April 21, 2008 7:10 PM | Feedback (13)