Here is a talk I gave on 26 September 2009 at the Houston TechFest. Thanks to Shawn Weisfeld for recording it!
Monthly Archives: January 2010
Architecture Analysis Webcast 9am Friday
The International Association of Software Architects is hosting a webcast Friday, January 22, 2010 at 9am. You can register here: https://www2.gotomeeting.com/register/579164130
It will be a 1.5 hour talk about architecture, but specifically structure of an application. Here are the full details:
Architecture Analysis – Reasoning About Structure
This presentation is about fundamentals. Layering is a basic concept of IT architecture. Layers help to seperate dependencies and to decouple concerns. Most of the industry does layering in name only. It’s lip service. In these slides and accompanying commentary we will explore the concepts of layering and isolation of dependencies and it’s impact on the success of your architecture.
When:
Friday, January 22, 2010
9:00 AM – 10:30 AM CST
Constructor over-injection smell – follow up
This is a follow up to my article: “Constructor over-injection anti-pattern”. I’ve title this a bit differently because as with any heuristic, there are degrees of goodness and smelliness. Constructor over-injection, therefore, is a code smell, I believe. Without concrete do’s and don’ts, I believe it doesn’t deserve the explicit title of “anti-pattern”.
I have expanded the code sample (and it is available as a zip or tar file here).
I’ve modified the OrderProcessor as shown here. I believe this is more egregious and is more of an obvious example. Small samples get so trivial, that the pain show doesn’t seem like a problem at all, but when injected constructor arguments increase (think 5, 10, 20), the pain is greater.
1: namespace DIAntiPattern
2: {
3: public class OrderProcessor : IOrderProcessor
4: {
5: private readonly IOrderValidator _validator;
6: private readonly IAccountsReceivable _receivable;
7: private readonly IRateExchange _exchange;
8: private readonly IUserContext _userContext;
9:
10: public OrderProcessor(IOrderValidator validator,
11: IAccountsReceivable receivable,
12: IRateExchange exchange, IUserContext userContext)
13: {
14: _validator = validator;
15: _receivable = receivable;
16: _exchange = exchange;
17: _userContext = userContext;
18: }
19:
20: public SuccessResult Process(Order order)
21: {
22: bool isValid = _validator.Validate(order);
23: if (isValid)
24: {
25: Collect(order);
26: IOrderShipper shipper = new OrderShipperFactory().GetDefault();
27: shipper.Ship(order);
28: }
29:
30: return CreateStatus(isValid);
31: }
32:
33: private void Collect(Order order)
34: {
35: User user = _userContext.GetCurrentUser();
36: Price price = order.GetPrice(_exchange, _userContext);
37: _receivable.Collect(user, price);
38: }
39:
40: private SuccessResult CreateStatus(bool isValid)
41: {
42: return isValid ? SuccessResult.Success : SuccessResult.Failed;
43: }
44: }
45: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Notice specifically that IRateExchange is NOT a dependency of OrderProcessor at all. It is actually needed by an aggregate root, Order, so that it can do the price conversion based on exchange rates and prices. This particular design attempted put put that logic as close to the domain model as possible (inside an aggregate). This code works, but it requires the caller to pass through the dependency. Here is the Order class:
1: using System;
2:
3: namespace DIAntiPattern
4: {
5: public class Order
6: {
7: public DateTime Placed { get; set; }
8: public string OtherInfo { get; set; }
9: public Price GetPrice(IRateExchange exchange, IUserContext userContext)
10: {
11: User currentUser = userContext.GetCurrentUser();
12: Currency currency = userContext.GetSelectedCurrency(currentUser);
13: int priceInSelectedCurrency = exchange.Convert(GetPrice(), currency);
14: var price = new Price{Currency = currency, Value = priceInSelectedCurrency};
15: return price;
16: }
17:
18: private int GetPrice()
19: {
20: //do work to aggregate prices from line items, and total up order.
21: return 1000;
22: }
23: }
24: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
There are plenty of options to fix this code, and I’m sure you will comment on the one you prefer. The SMELL here is constructor over-injection. The injected dependency isn’t a dependency, and another dependency is sometimes a dependency of a particular supported operation of a class.
However you fix this example, IRateExchange does not deserve to be constructor-injected into OrderProcessor. Next, LineItem will need a dependency to calculate something, and then the dependency has to flow through from an object that is managed by the IoC container.
IoC containers are just tools, but we still have to be careful of constructor over-injection.
I welcome the comments, and don’t forget to download the code (and fork it to post about your own counter example).
Constructor over-injection anti-pattern
Check out http://jeffreypalermo.com/blog/constructor-over-injection-smell-ndash-follow-up/ for a follow-up
I’m interested in structure. We hear lots of talk about convention over configuration. I’m all for structure over convention (over configuration). This post is about laying out some code that is an anti-pattern. It uses 100% constructor injection. This code makes it easy for a container to bootstrap the construction of the objects, but let’s take a look at why this specific scenario is an anti-pattern and what design would be better.
Example
I have an order processing application that gets 10 orders at a time and tries to process them to shipping. There are some validation steps, and then there is some work to ship the order. Here is our Main() method:
1: public static void Main()
2: {
3: DateTime startTime = DateTime.Now;
4: Console.WriteLine("Begin: " + startTime.TimeOfDay);
5: IEnumerable<Order> orders = GetNextTenOrders();
6:
7: foreach (var order in orders)
8: {
9: IOrderProcessor processor =
10: ObjectFactory.GetInstance<IOrderProcessor>();
11: SuccessResult successResult = processor.Process(order);
12: if (successResult == SuccessResult.Success)
13: {
14: RecordSuccess(order);
15: continue;
16: }
17:
18: ReportFailure(order);
19: }
20: DateTime endTime = DateTime.Now;
21: Console.WriteLine("End: " + endTime.TimeOfDay);
22: Console.WriteLine("Total time: " + endTime.Subtract(startTime));
23: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Let’s just run it:
Hmm. This little example code took over 7 seconds to run. Given that I’ve stubbed out all implementations, it should just breeze through. Here is the code of OrderProcessor, which is the class that is wired up to IOrderProcessor.
1: public class OrderProcessor : IOrderProcessor
2: {
3: private readonly IOrderValidator _validator;
4: private readonly IOrderShipper _shipper;
5:
6: public OrderProcessor(IOrderValidator validator, IOrderShipper shipper)
7: {
8: _validator = validator;
9: _shipper = shipper;
10: }
11:
12: public SuccessResult Process(Order order)
13: {
14: bool isValid = _validator.Validate(order);
15: if (isValid)
16: {
17: _shipper.Ship(order);
18: }
19:
20: return CreateStatus(isValid);
21: }
22:
23: private SuccessResult CreateStatus(bool isValid)
24: {
25: return isValid ? SuccessResult.Success : SuccessResult.Failed;
26: }
27: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Ok. Standard constructor injection. The anti-pattern is that one of these constructor arguments is really not a class dependency. The problem is that the constructor of OrderProcessor is too greedy. It doesn’t need IOrderShipper all the time, but it requires it ALL THE TIME. In fact, this company finds that 30% of orders are not valid the first time around. This particular implementation if IOrderShipper is a little bit expensive to create (like ISessionFactory of NHibernate is expensive to create). Forcing the creating when the false branch of the IF statement would render it useless is the smell.
Just for transparency, here is OrderShipper:
1: public class OrderShipper : IOrderShipper
2: {
3: public OrderShipper()
4: {
5: Thread.Sleep(TimeSpan.FromMilliseconds(777));
6: }
7:
8: public void Ship(Order order)
9: {
10: //ship the order
11: }
12: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
If you are truly coupling yourself to an abstraction (the interface), you should now care how the abstraction is implemented.
You might say: “What? Just move that long-running work to the Ship()
method!”
I would agree with that if you were designing the OrderShipper class. That only puts very elusive semantic coupling on all your classes. What you are really assuming is: I will code to interfaces. . . AND. . . as long as all of the implementations have constructors that don’t do much work, my whole application should work.
I am not decrying constructor injection as a useful method. In fact, we at Headspring do more constructor injection that abstract factory resolution. However, constructor arguments are API artifacts that shout loudly: “I can do NOTHING unless you pass these things in.” If that isn’t true, then don’t make the constructor announce a lie.
Cleaning up the smell
We can modify OrderProcessor to use an abstract factory and only require an IOrderShipper if the order is valid. Here is the new run:
Down from 7 seconds to less than 1. Why? Because the invalid order did not need to be shipped.
Here is the new OrderProcessor:
1: public class OrderProcessor : IOrderProcessor
2: {
3: private readonly IOrderValidator _validator;
4:
5: public OrderProcessor(IOrderValidator validator)
6: {
7: _validator = validator;
8: }
9:
10: public SuccessResult Process(Order order)
11: {
12: bool isValid = _validator.Validate(order);
13: if (isValid)
14: {
15: IOrderShipper shipper = new OrderShipperFactory().GetDefault();
16: shipper.Ship(order);
17: }
18:
19: return CreateStatus(isValid);
20: }
21:
22: private SuccessResult CreateStatus(bool isValid)
23: {
24: return isValid ? SuccessResult.Success : SuccessResult.Failed;
25: }
26: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Because we don’t require any implementation of IOrderShipper (line 15) unless the order is valid, our code is simpler. The class itself has 1 fewer class-level dependency, and only one method has the extra dependency. We employ a new type, OrderShipperFactory to keep the Inverted Control where OrderProcessor still doesn’t know the concrete implementation. There is one more step because factories need start-up configuration. If you are currently using an IoC container, you are familiar with start-up configuration because you configure the container’s global factory at start-up time. Here is the code for the factory:
1: public class OrderShipperFactory
2: {
3: public static Func<IOrderShipper> CreationClosure;
4: public IOrderShipper GetDefault()
5: {
6: return CreationClosure();//executes closure
7: }
8: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
And here is the method that configures this factory at start-up time:
1: private static void ConfigureFactories()
2: {
3: OrderShipperFactory.CreationClosure =
4: () => ObjectFactory.GetInstance<IOrderShipper>();
5: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Notice that the IoC container is still in control over creating the concrete implementations, but the abstract factories keep the production code dependencies inverted without being greedy toward optional dependencies.
Counter Argument
Ok, well, then I’ll just create ONE factory like this and use it everywhere:
1: public class GenericFactory
2: {
3: public static Func<object> CreationClosure;
4: public T GetDefault<T>()
5: {
6: return (T) CreationClosure();//executes closure
7: }
8: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
This factory will ONLY work with an IoC container that has the same semantics as the one you are currently using. For instance, you still need to unit test the code that uses the factory, so you will have to implement a stub for the factory (and unit tests don’t have expensive dependencies like IoC containers). Bottom line: this API is hard to implement by coding by hand. Any API that is hard to implement is a bad API. You want factories to be explicitly, and a method or class that can create anything????
Hmm. Isn’t that Activator.CreateInstance()? But even that is limited because it doesn’t do constructor argument chaining.
Bottom Line
Architecture and structure are agnostic of tools. IoC containers are just tools. Your design should be robust with or WITHOUT them. Taking away the IoC container should not make the structure of the application crumble.
Why do we use an IoC container, then? Because of the DRY Principle. It keeps us from having to write lots of abstract factory implementations that are essentially the same except for one line. It keeps each class from having a factory call from the no-arg constructor passing in the dependency to the greedy constructor. IoC containers have limited the need for hand-coded factories, but they have not eliminated the need.
The inspiration for this article came while I was sitting in a workshop where Matt Hinze was teaching a class about Inversion of Control (one of the public workshops that Headspring puts on each month). I thought about the response to Robert Martin’s recent blog post (which I quite like). Then a student in the class recalled a project where constructor injection was used everywhere. They felt the pain that is sure to come from this constructor over-injection anti-pattern.
One final word: Don’t be in a hurry to agree or disagree with me. Take the time to ponder.
UPDATE: Good discussion below. I’ve expanded the code sample in an attempt to make this code smell more obvious (because in reality, we don’t fret over 2 constructor arguments. We fret about 5, 10, 20). New post here: http://jeffreypalermo.com/blog/constructor-over-injection-smell-ndash-follow-up/
Headspring MVC Boot Camp review
Jimmy Bogard taught the last installment of Headspring’s ASP.NET MVC Boot Camp training class last week. Charlie Solomon wrote a thorough review of the class. It was a very interesting read. Here is an excerpt:
“Great training, I would highly recommend anything these guys offer. I enjoy a fast pace, hands on (lots of programming exercises) style of training, and that’s what I got. Jimmy is a good teacher… I already knew that, since I’ve been reading his blog for the past year or so and I’ve picked up a lot a good pointers to things that help me in my work. He is also a good communicator teaching in front of a room full of diverse programmers. If you are new to MVC like I am, this course is great for introducing concepts that may be new to web forms programmers… but like the MVC book that Jimmy co-authored, the class goes way beyond the introduction by showing how they leverage some of the features in MVC (ActionFilters and ResultFilters, HtmlTemplates, Model binding and validation…) to build applications quickly that are easier to maintain. We used the release candidate of ASP.Net MVC 2 in class.”
DVCS (Distributed Version Control System): support for agile?
Glenn Block began an interesting thread on the public “altnetseattle” email list on Thursday:
“Hi guys
I know we just finished a nice thread about GIT, whether it’s hot and why.
We also talked about alternatives like Bazzar, Mercurial, etc…A different question I have is do these tools (regardless of which one you
use) actually support Agile development? Would you consider them an integral
part of the agile shop of the future, or is it just "the way" to do it
regardless. What I am really asking is are there specific benefits for those
doing agile development?Thanks
Glenn” – Glenn Block
Now, Glenn is one of those really smart guys. You can learn more from someone not by listening to what answers he gives; but by what questions he asks.
The thread has some great discussion, but here is my answer that I posted:
“Every VCS supports agile. Agile is the way the team works with the VCS, not the other way around. DVCS systems make branch-per-developer seamless. With Subversion, it is easy to have multiple people working off the trunk, and the merging is great. Tools like JetBrains TeamCity will do auto-patch-upload and run the private build remotely at any time so you have build-per-developer without waiting for the local machine.
TFS and VersionManager need more work in order to have multiple people making changes to the same files simultaneously, but agile teams still use them.
Also, the VCS features have a lot more to do with supporting a team SIZE, not a team PROCESS or PHILOSOPHY. A BDUF team of 2 and an agile team of 2 will probably have similar experiences in wide range of VCS’s. BDUG teams of 10 and agile teams of 10 will have VERY different experiences based on the VCS in place.
I think the essence of the situation is providing the most versioned "levels of undo" possible. In John Maeda’s The Laws of Simplicity book, he talks about how putting "undo" everywhere makes the experience of using a product seem simpler. It may not be simpler, but the impression is that it IS simpler. With Git (the only DVCS I have experience with),:
- I can revert a local commit
- I can revert a push to my fork
- I can revert a pull to the master
- I can revert a commit on the master
- I can revert a push to the master
With a centralized system, I can only revert a commit to the centralized branch (along with roll-your-own branch commits and merges).
We need only look at some of the large, fast-moving projects hosted with Git to see that is "supports" whatever it is that we are labeling "agile" these days. http://git-scm.com/
Best regards,
Jeffrey Palermo”
Charlie Poole, a lead committer on the NUnit open source project, weighed in early and posted something so profound, I just have to share it.
Charlie has so much experience in software that when he speaks, I listen intently. I have had the please of meeting him at several Alt.Net conferences. Here is his first post on the thread:
“Hi Glenn,
I consider DVCS to be agile on a philosophical and practical level.
Philosophically, agile is supposed to be *not* so much about tools. DVCSs
remove many of
the restrictions imposed by centralized tools. Most of them work well in
either mode, so a team
can set up it’s own practices and not be limited by what the tool can do.One proof of this is that some shops forbid DVCSs because they don’t give
enough central
control over developers – a very non-agile viewpoint.In practical terms, a DVCS allows me to work how and where I want, creating
a feature
branch if I need to, or keeping a common personal branch for small changes.
I can work
quickly on a spike, throwing it all away or merging it in as I like. This is
something I used
to do with multiple workspaces under SVN or CVS, but when I wanted to merge
two
different workspaces without touching the repository, I had to do it
manually.So I’d say that DVCSs are a win no matter how you develop, but are a
particularly
good fit for agile development.Charlie”
Charlie has undoubtedly used twice as many Version Control Systems than I have, so when he compares a DVCS to all the others in his past and calls it a “win”, that’s a pretty strong endorsement.
My experience with Git (the only DVCS I’ve used so far) has been positive. I use it for MvcContrib and for working on the manuscript for ASP.NET MVC 2 in Action.
Subversion seems to be losing favor among version control users
I haven’t done a study, but Subversion is a very popular Version Control System, but it is centralized. Centralized VCSes have the notion of a “patch” since you can’t share source without submitting it to the central Version Control server. Decentralized version control systems are proving to scale much better and actually be more usable in the process. Git and Mercurial are two of the distributed version control systems that seem to be proving themselves among their customer base. I have experience with Git, and at Headspring, we are considering running a pilot project on it since we have successfully moved one of our sponsored open-source projects onto the system (MvcContrib).
The point of this post isn’t to point to any single product and say “use this product”. It is merely to recognize what I see as a shift toward distributed version control. Because every developer has a cloned repository, the only operations that require a network connection are push and pull (this probably differs a bit). Conceptually, the advantages of distribution of version control is that it is fast, really fast, to show logs, commit, roll back – because you don’t have to wait for the network to do so.
With the distributed workflow, you perform the following steps:
- Clone the remote repository
- Modify some of the source
- Commit your changes locally
- Do some more modifications and commit
- Perhaps roll back some of your modifications
- More modifications and commit
- “Pull” from the remote repository (merge remote changes to your local repository)
- Run your private build to ensure the software is stable
- “Push” changes to the remote repository (now the rest of the team can get your changes)
You still go through this cycle very quickly, but it adds another level of being able to describe discreet changes.
We’ll see where the industry goes with distributed vs. centralized version control systems.
MVC 2 in Action book conducting public reviews
If you’d like to get an early glimpse of ASP.NET MVC 2 in Action, you can participate on the public email list at http://groups.google.com/group/mvc2inaction-discuss. The first bit of the manuscript is ready to review, and it consists of chapter 23, Data Access with NHibernate. You can also access every bit of code, text, and image at the book’s GitHub repository. With the first book, ASP.NET MVC in Action, Manning conducted an Early Access Program (MEAP). Manning will still conduct it, but our program is a early, early access program. In other words, you can have access to the unedited, perhaps dirty, manuscript and help as it drives forward to polish and completion.
As each piece of the manuscript is complete, we will post it to this list and ask for feedback. Anyone who gives us constructive feedback on the list WILL be thanked in the acknowledgements section at the front of the book when it goes to print. Also, you will have influence in shaping this book so that it is as good as it can be.
Our hope is that this book serves the .Net community that is working ASP.NET MVC 1 and ASP.NET MVC 2 applications.
The author team consists of:
If this book project interests you, and if you know people who should be involved, please blog, tweet, and otherwise post a link to this announcement.