Adaptive sample: Sprint 1 - Adaptive sample - Adaptive Code via C#. Agile coding with design patterns and SOLID principles (2014)

Adaptive Code via C#. Agile coding with design patterns and SOLID principles (2014)

Part III: Adaptive sample

Chapter 11. Adaptive sample: Sprint 1

In this chapter, you will

Image Observe the team’s first sprint planning session.

Image Follow the implementation and evolution of the first user stories.

Image Observe the team’s sprint demo and retrospective.

In this chapter, the Trey Research team implements its first four user stories. These are the only stories that the team has assigned to Sprint 1 of the project. The team has set the following sprint goal:

To demonstrate a dynamic list of rooms, to be able to create a new room, to view the messages written in a room, and for at least two users to be able to share messages in the room

By defining a sprint goal, the team set something challenging but achievable to be completed within the sprint. All work should be aligned to the sprint goal so that the team knows that it is on track and working on only what the client has asked for.

Planning

The team arranges a planning meeting for the sprint and takes to it the stories that relate to the sprint goal. For this sprint, the stories that the team will implement are:

Image I want to create rooms for categorizing conversations.

Image I want to view a list of rooms that represent conversations.

Image I want to view the messages that have been sent to a room.

Image I want to send plain text messages to other room members.

The team begins its discussion.

PETRA: Okay, four stories to talk about, but these are the first to be implemented, so I expect there are some key decisions to be made here.

STEVE: Perhaps, yes. Certainly we need to consider a few technologies before we begin. Does anyone have any suggestions?

DIANNE: This is a web application, so we should probably go with something that we are all comfortable and familiar with. ASP.NET MVC seems the obvious choice. Does everyone agree?

STEVE: I’m happy with that.

DAVID: Hmm. I think this is a perfect Node.js application, to be honest.

TOM: I have no experience with Node, unfortunately. Does anyone else?

STEVE: Not really, no. I think Dianne is right to say that we should stick to what we know.

DIANNE: With MVC, we already have much of the code structure set out for us. Look at this sequence diagram.

Dianne shows the team the UML sequence diagram in Figure 11-1.

Image

FIGURE 11-1 Dianne’s sequence diagram generalizes the structure of the application.

STEVE: Okay, I understand what you’ve done here. That looks okay to me, for now.

DAVID: It doesn’t look like there’s anything to do with rooms or messages.

DIANNE: Yes, I’ll explain. It’s quite generic. Rather than create a sequence diagram for every user story, I decided to generalize the solution so that we can tell what classes we will need—in the short term, at least.

DAVID: Oh, I see. So Get data is a placeholder for Get room and Get messages, right?

DIANNE: Yes, you’ve got it.

STEVE: So, what’s the service part? Will it be event-driven messaging or some kind of database that we poll?

DIANE: HTTP is a disconnected, stateless protocol. We will have to poll the server for messages.

DAVID: But only the messages since the last check. We don’t want to return all messages all the time.

STEVE: Yes, that wouldn’t scale well.

DIANNE: Speaking of which, is this really a scalable solution?

PETRA: Good point. Although the client wants to support only 20 users in the short term, you can tell that this is going to expand quite quickly. There’s no way to know what the upper limit might be. We shouldn’t limit the application’s scalability.

STEVE: Perhaps not, no. But, as long as we build adaptability into the code from the outset, we should be able to discover the most appropriate architecture as we progress.

TOM: What about the user interface? If we’re going to demonstrate this next week, it should at least look respectable.

PETRA: Tom’s right—a lot of confidence from clients is lost by functionally great but aesthetically poor demonstrations.

DAVID: Well, MVC uses bootstrap, so we could implement a couple of bootstrap templates. We’ll need a page for the room list and a page for the room’s messages. At least then it will look a bit modern, if a little minimalist.

PETRA: Don’t worry about minimalism. I think they will probably want this application to be themed by the user, anyway. Any effort we spend on perfecting the user interface would probably be wasted at such an early stage.

STEVE: Tom, what about testing? Is there anything here you’re worried about?

TOM: No, I made sure that the sprint goal was limited to just two users so that I can defer the automated load testing until later. At this point we can just focus on manually testing things until I get some automation set up. I’ll probably want you three to unit test, though, so let me know if you need help with any of that.

STEVE: Brilliant—I think we’re done for now. We can discuss things in further detail as and when we need to.

The meeting ends, and the team is ready to implement its first story.

“I want to create rooms for categorizing conversations.”

A couple of days later, David indicates that he is ready for a code review of the room creation story. Dianne comes over to David’s desk, and the two discuss the code. The goal of this discussion is to identify both positive and negative aspects of the chosen implementation. Peer reviews such as this offer developers a chance to identify whether something is not up to the required standard or lacks adaptability, so that final changes can be made before the code is committed to source control.

The controller

When David starts implementing the controller, he calls Dianne over to ask for her input.

DAVID: So, I’ve used the MVC pattern as you suggested. The controller itself doesn’t do much yet—it is merely delegating down to an IRoomRepository interface to interact with whatever persistent storage we put in place.

DIANNE: Let’s look at the controller code now.

David shows Dianne the controller code in Listing 11-1.

LISTING 11-1 The RoomController is the entry point of the Create requests.


public class RoomController : Controller
{
private readonly IRoomRepository roomRepository;
public RoomController(IRoomRepository roomRepository)
{
Contract.Requires<ArgumentNullException>(roomRepository != null);

this.roomRepository = roomRepository;
}

[HttpGet]
public ActionResult List()
{
return View();
}

[HttpGet]
public ActionResult Create()
{
return View(new CreateRoomViewModel());
}

[HttpPost]
public ActionResult Create(CreateRoomViewModel model)
{
ActionResult result;

if(ModelState.IsValid)
{
roomRepository.CreateRoom(model.NewRoomName);

result = RedirectToAction("List");
}
else
{
result = View("Create", model);
}

return result;
}
}


DIANNE: Okay, this looks good. We’ve got dependency injection for the IRoomRepository, so that gives us some flexibility. And it is enforced with code contracts, which is great.

DAVID: Yes—I also ensure that the preconditions are in place with unit tests. I’ll show them to you if you want.

DIANNE: That would be great, but could you just explain the Create method for the POST request handler, please? Specifically, why does it redirect after delegating to the CreateRoom method on the repository?

DAVID: That’s the Post-Redirect-Get pattern. Basically, if we just return to the List view directly at this point, any attempts to refresh the page by the user will result in a second POST request. This would mean that we try to create another room with the same name, and it just doesn’t feel right from a user experience perspective.

DIANNE: Excellent! This looks really good for now.

DAVID: One thing I’m not so sure of is directly instantiating a new CreateRoomViewModel. Shouldn’t I use a factory for this?

DIANNE: Good question. I would say no—not in this case. The reason is that there is unlikely to be any variation in the returned type. Viewmodels like this are tailored to a specific use, and it would be needless indirection to delegate to a factory here.

Controller unit tests

David opens the file containing the unit tests, as shown in Listing 11-2, and asks Dianne to peer review his work.

DAVID: Here are the unit tests for the RoomController constructor.

LISTING 11-2 Validating the RoomController constructor through unit tests.


[Test]
public void ConstructingWithoutRepositoryThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => new RoomController(null));
}

[Test]
public void ConstructingWithValidParametersDoesNotThrowException()
{
Assert.DoesNotThrow(() => CreateController());
}


DAVID: The first one ensures that we always have a valid reference for the IRoomRepository field in the RoomController.

DIANNE: Yes, it is part of the implied contract of the RoomController that it always has a valid, not-null repository field.

DAVID: These next two tests are asserting the behavior of the Create method used to service GET requests.

David scrolls down through the tests file and finds the code shown in Listing 11-3.

LISTING 11-3 Unit tests for the GET request of the Create action.


[Test]
public void GetCreateRendersView()
{
var controller = CreateController();

var result = controller.Create();

Assert.That(result, Is.InstanceOf<ViewResult>());
}

[Test]
public void GetCreateSetsViewModel()
{
var controller = CreateController();

var viewResult = controller.Create() as ViewResult;

Assert.That(viewResult.Model, Is.InstanceOf<CreateRoomViewModel>());
}


DAVID: We need to know two things: that the request gives a ViewResult instance, and that the Model property is of the expected type, CreateRoomViewModel.

DIANNE: Could you unify these into one unit test with two assertions?

DAVID: I suppose I could, but then it wouldn’t be immediately clear from the name of the unit test which assertion had failed. I prefer to write quite fine-grained tests with as few assertions as make sense. When a unit test fails, it is then easy to identify the errant behavior.

DIANNE: That’s fine. What about the POST request side of the Create action, the one that delegates to the service?

DAVID: Here it is.

David scrolls down further and shows Dianne the unit tests of Listing 11-4.

LISTING 11-4 Unit tests for the POST request of the Create action.


[Test]
[TestCase(null)]
[TestCase("")]
[TestCase(" ")]
public void PostCreateNewRoomWithInvalidRoomNameCausesValidationError(string roomName)
{
var controller = CreateController();

var viewModel = new CreateRoomViewModel { NewRoomName = roomName };

var context = new ValidationContext(viewModel, serviceProvider: null, items: null);
var results = new List<ValidationResult>();

var isValid = Validator.TryValidateObject(viewModel, context, results);

Assert.That(isValid, Is.False);
}

[Test]
[TestCase(null)]
[TestCase("")]
[TestCase(" ")]
public void PostCreateNewRoomWithInvalidRoomNameShowsCreateView(string roomName)
{
var controller = CreateController();

var viewModel = new CreateRoomViewModel { NewRoomName = roomName };
controller.ViewData.ModelState.AddModelError("Room Name", "Room name is required");
var result = controller.Create(viewModel);

Assert.That(result, Is.InstanceOf<ViewResult>());

var viewResult = result as ViewResult;
Assert.That(viewResult.View, Is.Null);
Assert.That(viewResult.Model, Is.EqualTo(viewModel));
}

[Test]
public void PostCreateNewRoomRedirectsToViewResult()
{
var controller = CreateController();

var viewModel = new CreateRoomViewModel { NewRoomName = "Test Room" };
var result = controller.Create(viewModel);

Assert.That(result, Is.InstanceOf<RedirectToRouteResult>());

var redirectResult = result as RedirectToRouteResult;
Assert.That(redirectResult.RouteValues["Action"], Is.EqualTo("List"));
}

[Test]
public void PostCreateNewRoomDelegatesToRoomRepository()
{
var controller = CreateController();

var viewModel = new CreateRoomViewModel { NewRoomName = "Test Room" };
controller.Create(viewModel);

mockRoomRepository.Verify(repository => repository.CreateRoom("Test Room"));
}


DAVID: The first two tests assert that the room name is a required field—it cannot be omitted—and that a validation error drops the user back at the room creation form.

DIANNE: I like your use of the TestCase attribute to provide the unit test with some erroneous room names.

DAVID: Thanks. I thought it would allow me to write fewer tests.

DIANNE: So far, so good. What’s next?

The room repository

David is unsure of his implementation for the room repository. He opens the file shown in Listing 11-5 so that Dianne can offer her opinion.

DAVID: I’ve created an implementation of the IRoomRepository interface for ADO.NET. Here it is.

LISTING 11-5 The AdoNetRoomRepository allows Room data to be stored in any ADO.NET-compatible database.


public class AdoNetRoomRepository : IRoomRepository
{
public AdoNetRoomRepository(IConnectionIsolationFactory factory)
{
this.factory = factory;
}

public void CreateRoom(string name)
{
factory.With(connection =>
{
using(var transaction = connection.BeginTransaction())
{
var command = connection.CreateCommand();
command.CommandText = "dbo.create_room";
command.CommandType = CommandType.StoredProcedure;
command.Transaction = transaction;
var parameter = command.CreateParameter();
parameter.DbType = DbType.String;
parameter.ParameterName = "name";
parameter.Value = name;
command.Parameters.Add(parameter);

command.ExecuteNonQuery();
}
});
}

private readonly IConnectionIsolationFactory factory;
}


DAVID: I’ve used the Factory Isolation pattern here, and I’m delegating down to a factory interface to control the lifetime of the database connection object. The body of the Create-Room method is pretty standard ADO.NET code aside from there.

DIANNE: Hmm, I’m not so sure that the Factory Isolation pattern is warranted here. Let me just ask Steve.

Factory Isolation pattern misapplied

Dianne beckons Steve over to get a second opinion on David’s use of the Factory Isolation pattern.

STEVE: How can I help?

DIANNE: Well, look at this class. David’s used the Factory Isolation pattern here, but I’m just not sure it’s appropriate.

STEVE: Hmm, indeed. I think I understand the problem here. You’re using ADO.NET here, right? Let’s have a look at the implementation of the factory.

David opens the file and shows Dianne and Steve the code in Listing 11-6.

LISTING 11-6 The isolation factory aims to manage the lifetime of a database connection.


public class AdoNetConnectionIsolationFactory : IConnectionIsolationFactory
{
private readonly IApplicationSettings applicationSettings;
private readonly DbProviderFactory dbProviderFactory;
public AdoNetConnectionIsolationFactory(IApplicationSettings applicationSettings)
{
this.applicationSettings = applicationSettings;
this.dbProviderFactory = DbProviderFactories
.GetFactory(applicationSettings.GetValue("DatabaseProviderName"));
}

public void With(Action<IDbConnection> action)
{
using(var connection = dbProviderFactory.CreateConnection())
{
connection.ConnectionString = applicationSettings
.GetValue("ProsewareConnectionString");
connection.Open();

action(connection);
}
}

}


STEVE: The DbProviderFactory.CreateConnection method returns an IDbConnection.

DAVID: Yes, that’s right.

DIANNE: I understand the problem now. The Factory Isolation pattern is only applicable when the product of the factory may or may not implement IDisposable. The IDbConnection interface inherits from IDisposable, so it effectively forces all implementations to provide a public Dispose method.

STEVE: Exactly. So you don’t need to use factory isolation because...

DAVID: Because I can just add in a using block, instead.

DIANNE: That’s right.

DAVID: Okay, so should this just be a normal factory, instead?

DIANNE: I would say no. If you look at what’s happening in this class, it’s effectively hiding the DbProviderFactory from clients. I think the factory is needless indirection in this case.

STEVE: I agree.

DAVID: But that means I’ll have to call DbProviderFactory directly from the room repository implementation. Isn’t that inflexible, especially because DbProviderFactory is a static class?

STEVE: That’s true—it would be nice if DbProviderFactory wasn’t a static class. But the room repository implementation depends on ADO.NET, so it isn’t really pollution. The introduction of the factory has made public something that is a concern only internally to the repository’s implementation.

DAVID: Okay, that makes sense. And we can ignore this static class call because it is generic enough as it is, right?

DIANNE: Yes. Consider what meaningful decoration or adaptation you might make to a DbProviderFactory if it was an interface. Any alternative behavior is more likely to be injected at a higher level—around the IRoomRepository interface.

DAVID: I’ve got it. Let me make these changes, and I’ll get back to you.

Refactoring

David sets to work on refactoring the room repository, resulting in the code shown in Listing 11-7. He calls Dianne and Steve over to assess the changes.

LISTING 11-7 The refactored room repository uses the DbProviderFactory directly.


public class AdoNetRoomRepository : IRoomRepository
{
private readonly IApplicationSettings applicationSettings;
private readonly DbProviderFactory databaseFactory;
public AdoNetRoomRepository(IApplicationSettings applicationSettings,
DbProviderFactory databaseFactory)
{
Contract.Requires<ArgumentNullException>(applicationSettings != null);
Contract.Requires<ArgumentNullException>(databaseFactory != null);

this.applicationSettings = applicationSettings;
this.databaseFactory = databaseFactory;
}

public void CreateRoom(string name)
{
using(var connection = databaseFactory.CreateConnection())
{
connection.ConnectionString =
applicationSettings.GetValue("ProsewareConnectionString");
connection.Open();

using(var transaction = connection.BeginTransaction())
{
var command = connection.CreateCommand();
command.CommandText = "dbo.create_room";
command.CommandType = CommandType.StoredProcedure;
command.Transaction = transaction;
var parameter = command.CreateParameter();
parameter.DbType = DbType.String;
parameter.ParameterName = "name";
parameter.Value = name;
command.Parameters.Add(parameter);

command.ExecuteNonQuery();
}
}
}

}


DAVID: I made a few changes. I’ve integrated the code from the connection factory into the repository class. I wasn’t happy with the arbitrary use of the DbProviderFactories static class. The DbProviderFactory class is at least an abstract class, which gives us some extension points that we can work with. This way, we’re using normal dependency injection, but instead of an interface, the injection is an abstract class.

DIANNE: That’s great. I think you’ve made good changes there.

STEVE: Could you show me the Inversion of Control container’s registration configuration for the DbProviderFactory? I’m interested in how you’re registering this.

David shows Steve and Dianne the Unity registration code shown in Listing 11-8.

LISTING 11-8 The Unity Inversion of Control (IoC) registration for the DbProviderFactory.


container.RegisterType<DbProviderFactory>(new InjectionFactory(c =>
DbProviderFactories.GetFactory(
c.Resolve<IApplicationSettings>().GetValue("DatabaseProviderName"))));


STEVE: That’s fine. I expected it would be a little unusual and contrived due to the static nature of the class involved, but I think you’ve done well to abstract it away from the repository implementation.

DIANNE: I think this is good to be integrated now, don’t you, Steve?

STEVE: Yes, I think that’s our first story completed.

David commits the code, and the team moves on to the next story.

“I want to view a list of rooms that represent conversations.”

The next day, David completes the second story—viewing the list of all created rooms. Before committing the code to source control, he asks Dianne to review his work.

DAVID: I’ve made some changes to the RoomController so that it supports reading the rooms and displaying them on a page.

DIANNE: Okay, let’s start there. Open up the RoomController and show me what’s new.

David shows Dianne the code in Listing 11-9. To keep this snippet small and focused, the room creation actions have been omitted.

LISTING 11-9 The RoomController has a new action for listing the rooms.


public class RoomController : Controller
{
private readonly IRoomRepository roomRepository;
private readonly IRoomViewModelMapper viewModelMapper;
public RoomController(IRoomRepository roomRepository, IRoomViewModelMapper mapper)
{
Contract.Requires<ArgumentNullException>(roomRepository != null);
Contract.Requires<ArgumentNullException>(mapper != null);

this.roomRepository = roomRepository;
this.viewModelMapper = mapper;
}

[HttpGet]
public ActionResult List()
{
var roomListViewModel = new RoomListViewModel();

var allRoomRecords = roomRepository.GetAllRooms();

foreach(var roomRecord in allRoomRecords)
{
roomListViewModel.Rooms
.Add(viewModelMapper.MapRoomRecordToRoomViewModel(roomRecord));
}

return View(roomListViewModel);
}

}


DAVID: There’s a new constructor argument here—a mapper—the presence of which is enforced with code contracts. I added a new unit test for that, too.

DIANNE: Okay, that’s good. There’s one change I would make here, though. Talk me through the List method, and I’ll let you know what I think we can do to simplify this class.

DAVID: It has two parts, really. First, the room repository is queried to retrieve all room records. However, those room records are models, not viewmodels, so I’ve introduced some mapping from one data type to another. This is delegated down to the mapper interface. After the data is converted to RoomViewModel objects, we can pass that to the view so that it can render the list of rooms.

DIANNE: I think there’s a leak in one of the abstractions here. The RoomRecord class, which is what the IRoomRepository returns, belongs down at the data persistence layer. I’m not sure whether it should have propagated all the way up to this controller. This is the underlying reason that the mapper is necessary.

DAVID: But what other option do I have? I can only retrieve RoomRecord instances, but I also need to return RoomViewModel objects.

DIANNE: Absolutely. But there is no need for this controller to know about RoomRecords and the fact that they need to be mapped. Instead, how about making the controller depend on neither the IRoomRepository nor the IRoomViewModelMapper but on a new interface instead? We could call it an IRoomViewModelService. This would allow the controller to be ignorant of the RoomRecord. One implementation of this new service would use the room repository and the mapper that you have here to return RoomViewModel instances, which is the only data that the controller needs to know about here.

DAVID: I understand. However, what about the existing CreateRoom call that exists on the IRoomRepository interface? Without a repository, this controller will not be able to call that method.

DIANNE: Good point. I would suggest that you use interface segregation here. Instead of the IRoomViewModelService, you could have an IRoomViewModelReader and an IRoomViewModelWriter. That gives us the option in the future of varying the implementation of the read and write sides of the requests.

DAVID: Okay, I think my unit tests are going to change quite a lot due to this. Instead of showing them to you now, I will perform these refactors and get back to you.

DIANNE: How will this affect our delivery of the sprint goals? Will this compromise our meeting the deadline?

DAVID: No, the refactors are quite small. I should be able to complete this in an hour or two.

DIANNE: Sounds good.

David sets to work fixing the issues that Dianne had pointed out.

Refactoring

David spends a few hours refactoring his code to make Dianne’s suggested changes. He then calls Dianne over to inspect the results.

DAVID: This is the controller after the refactors. What do you think?

Listing 11-10 shows the new controller after refactoring.

LISTING 11-10 The controller has been refactored to depend on reader and writer interfaces.


public class RoomController : Controller
{
private readonly IRoomViewModelReader reader;
private readonly IRoomViewModelWriter writer;
public RoomController(IRoomViewModelReader reader, IRoomViewModelWriter writer)
{
Contract.Requires<ArgumentNullException>(reader != null);
Contract.Requires<ArgumentNullException>(writer != null);

this.reader = reader;
this.writer = writer;
}

[HttpGet]
public ActionResult List()
{
var roomListViewModel = new RoomListViewModel(reader.GetAllRooms());

return View(roomListViewModel);
}

[HttpGet]
public ActionResult Create()
{
return View(new RoomViewModel());
}

[HttpPost]
public ActionResult Create(RoomViewModel model)
{
ActionResult result;

if(ModelState.IsValid)
{
writer.CreateRoom(model.Name);

result = RedirectToAction("List");
}
else
{
result = View("Create", model);
}

return result;
}

}


DIANNE: This has removed the dependency on the mapper quite nicely. The injection of a reader and a writer means that we have the option of varying the implementation in the future, in case we move toward a CQRS architecture.

DAVID: Could you remind me what CQRS is, please?

DIANNE: Command/Query Responsibility Segregation. It’s where the commands and the queries of your application do not align: they are asymmetrical. In our case, we might write to transactional storage, but we might read from nontransactional document storage. I think Steve has been tinkering with a possible architecture if this does not scale well, as we suspect.

DAVID: I remember now—that sounds interesting. Shall we take a look at the IRoomViewModelReader and IRoomViewModelWriter implementation?

Dianne nods, and David opens the file containing that class, as shown in Listing 11-11.

LISTING 11-11 A service that more closely resembles the original controller code.


public class RepositoryRoomViewModelService : IRoomViewModelReader, IRoomViewModelWriter
{
private readonly IRoomRepository repository;
private readonly IRoomViewModelMapper mapper;
public RepositoryRoomViewModelService(IRoomRepository repository,
IRoomViewModelMapper mapper)
{
Contract.Requires<ArgumentNullException>(repository != null);
Contract.Requires<ArgumentNullException>(mapper != null);

this.repository = repository;
this.mapper = mapper;
}

public IEnumerable<RoomViewModel> GetAllRooms()
{
var allRooms = new List<RoomViewModel>();
var allRoomRecords = repository.GetAllRooms();
foreach(var roomRecord in allRoomRecords)
{
allRooms.Add(mapper.MapRoomRecordToRoomViewModel(roomRecord));
}
return allRooms;
}

public void CreateRoom(string roomName)
{
repository.CreateRoom(roomName);
}

}


DAVID: I decided to implement both interfaces in one class, because they both shared a dependency on the IRoomRepository. However, I think this resembles the original controller class—what have we gained with this refactor?

DIANNE: The main benefit is that the controller is now more focused on its main responsibilities. Instead of being responsible for orchestrating the mapping from records to viewmodels, it can now focus on responding to validation. The fact that the controller no longer has knowledge of the RoomRecord class is compelling enough.

DAVID: Yes, I suppose it was starting to do too much, wasn’t it?

DIANNE: It didn’t really obey the single responsibility principle. If we weren’t using a repository, we wouldn’t need a mapper. This would be a change lower down in the architectural layers. Controllers should not be concerned with such changes.

David and Dianne agree that this story is now ready to be committed.

“I want to view the messages that have been sent to a room.“

Dianne works on the next story in tandem with David, by using pair programming. While Dianne writes the code, David looks on and offers suggestions.


Image Note

Pair programming is a common practice in Agile software development that originated with the Extreme Programming (XP) methodology: two programmers work together on a particular functionality. While one of the pair types, the other is able to consider the best way of implementing a method, class, or unit test.


DIANNE: Shall we start with the controller?

DAVID: Yes, that seems like a good place to begin.

DIANNE: We’re going to need a new HttpGet handler for viewing room messages. What shall we call this?

DAVID: Something like GetMessages?

DIANNE: That would be good, but ASP.NET MVC uses the controller name and method name to form the URL for the request.

DAVID: Of course, I forgot. How about just calling it Messages? It is part of the RoomController, so the URL will be /Room/Messages.

DIANNE: Okay, that makes sense. We’ll also need a parameter for identifying which room’s messages we want to see.

DAVID: We’ve got the room ID, which uniquely identifies a room. Could we use that?

DIANNE: Is it an integer or a long?

DAVID: I thought an integer would suffice.

DIANNE: We’ll also need a new viewmodel for this view. Following the naming convention you’ve used so far, this should be MessageListViewModel, right?

DAVID: Yes. We can just pass an instance of it to the View method on the controller and return the resulting ViewResult.

DIANNE: The IRoomViewModelReader will need a new method for querying the messages in a room.

DAVID: I suppose that should be called GetRoomMessages, and it should also accept the room ID as a parameter.

DIANNE: I’ll define it on the interface and create a stub on any implementing classes so that we can determine how the controller changes might work.

Dianne creates the method shown in Listing 11-12.

LISTING 11-12 The Messages method retrieves all messages associated with a room, by ID.


[HttpGet]
public ActionResult Messages(int roomID)
{
var messageListViewModel = new MessageListViewModel(reader.GetRoomMessages(roomID));

return View(messageListViewModel);
}


DIANNE: This looks good. Let’s fill in the implementation of the IRoomViewModelReader.GetRoomMessages method.

DAVID: Okay, the only implementation so far makes use of an IRoomRepository interface. I think we’ll need a new IMessageRepository interface so that we can retrieve messages.

DIANNE: Agreed. We should inject it in just like the other dependencies.

DAVID: When we have that repository available in the class, we need to request from it the messages for a given room ID and pass them to the mapper.

DIANNE: So the mapper is responsible for converting records from the repository to viewmodels that the controller can use, right? It looks like there’s only an IRoomViewModelMapper available. It feels a little too much to create a new IMessageViewModelMapper. Can we rename this interface to something more generic?

DAVID: How about IViewModelMapper? That way it becomes useful for mapping all records to viewmodels.

DIANNE: How does this look?

Dianne shows David the code in Listing 11-13.

LISTING 11-13 The class that maps records to viewmodels now contains a method for retrieving room messages.


public class RepositoryRoomViewModelService : IRoomViewModelReader, IRoomViewModelWriter
{
private readonly IRoomRepository roomRepository;
private readonly IMessageRepository messageRepository;
private readonly IViewModelMapper mapper;
public RepositoryRoomViewModelService(IRoomRepository roomRepository,
IMessageRepository messageRepository, IViewModelMapper mapper)
{
Contract.Requires<ArgumentNullException>(roomRepository != null);
Contract.Requires<ArgumentNullException>(messageRepository != null);
Contract.Requires<ArgumentNullException>(mapper != null);

this.roomRepository = roomRepository;
this.messageRepository = messageRepository;
this.mapper = mapper;
}

public IEnumerable<MessageViewModel> GetRoomMessages(int roomID)
{
var roomMessages = new List<MessageViewModel>();
var roomMessageRecords = messageRepository.GetMessagesForRoomID(roomID);
foreach(var messageRecord in roomMessageRecords)
{
roomMessages.Add(mapper.MapMessageRecordToMessageViewModel(messageRecord));
}
return roomMessages;
}

}


DAVID: That looks good to me.

David and Dianne continue to implement the repository method IMessageRepository.GetMessageForRoomID(), which loads the message from the Microsoft SQL Server database. After this method is implemented, they move on to the next user story.

“I want to send plain text messages to other room members.”

David swaps places with Dianne to implement the final story of the sprint.

DAVID: This should be really easy now that we have a pattern in place for reading and writing data.

DIANNE: Sort of, yes. But remember, one of the requirements of this story is that the request will be made asynchronously.

DAVID: Oh. So it won’t be a full-page postback?

DIANNE: No, the user interface will send the data via an AJAX request.


Image Note

Some definitions: AJAX stands for Asynchronous JavaScript and XML. XML stands for Extensible Markup Language. AJAJ stands for Asynchronous JavaScript and JSON. JSON stands for JavaScript Object Notation.


DAVID: Hold on. You mean AJAJ, don’t you?

DIANNE: I suppose I do!

DAVID: What makes this controller action different, then?

DIANNE: Two things, really. When the ModelState.IsValid property is true, we should use the IRoomViewModelWriter to save the message. But we should then return the viewmodel as a JsonResult.

DAVID: And what do we do when the model state is not valid?

DIANNE: In that case, return an HttpStatusCodeResult with an HTTP 400 error.

DAVID: That’s a client error response code, isn’t it?

DIANNE: Yes. Specifically, it’s a Bad Request response.

David shows Dianne the code in Listing 11-14.

LISTING 11-14 The AddMessage method on the RoomController class.


[HttpPost]
public ActionResult AddMessage(MessageViewModel messageViewModel)
{
ActionResult result;

if(ModelState.IsValid)
{
writer.AddMessage(messageViewModel);

result = Json(messageViewModel);
}
else
{
result = new HttpStatusCodeResult(400);
}

return result;
}


DAVID: What about the AddMessage method on the IRoomViewModelWriter?

DIANNE: I think we can use the same pattern as before. This part of the code does not care whether it is called synchronously or asynchronously, so it will not change the pattern that we’ve established so far.

David proceeds to follow the same pattern that the IRoomViewModelWriter.CreateRoom method uses, which results in the AddMessage method shown in Listing 11-15.

LISTING 11-15 The AddMessage method on the RoomViewModelWriter class.


public void AddMessage(MessageViewModel messageViewModel)
{
var messageRecord = mapper.MapMessageViewModelToMessageRecord(messageViewModel);
messageRepository.AddMessageToRoom(messageRecord.RoomID, messageRecord.AuthorName,
messageRecord.Text);
}


With this change, and the creation of a further repository method to retain the message record in Microsoft SQL Server via ADO.NET, the story is complete.

David and Dianne have now finished the stories that were assigned to the sprint, just in time for the sprint demo.

Sprint demo

At the end of the week, the team arranges to meet the client and demonstrate its progress thus far. The stories to be completed are assembled, and the functionality of each one is shown. The client is invited to provide feedback and alter the direction in which to take the product from here.

The sprint demo has many benefits. First, it allows the team to ensure that it is always aligned with the client’s current wants and needs. The demonstration also motivates the team members to always produce their best work because they must be confident that they can show the current state of the product to the client. Clients also benefit because they are able to view tangible progress on the product at an early stage and regularly throughout development. If a client wants to change anything about how the software works, the product demonstration is a great time to do so. The backlog of user stories can be altered and reprioritized as a result, and the software will immediately change direction to meet the needs of the client.

First demonstration of Proseware

On demonstration day, the team assembles in a meeting room with a representative from the client company. The team briefly discusses how the sprint has progressed and prepares to show the current state of the software.

Unfortunately at such a key time, it is discovered that the projector in the meeting room is not working correctly. This delays the presentation for a few minutes while a technician who can correct the problem is found. After the projector is up and running, the team discovers that the resolution of the screen is far lower than that of the development machines. This degrades the appearance of the user interface, making it harder for the client to understand which parts are where on the page.

The team apologizes and the client seems understanding, though a little disheartened that the application doesn’t look as his company had hoped. After some tweaking of display settings, the application starts to look a little better, though it still does not appear exactly as it did in the development environment.

The team runs through the four stories that have been implemented during the sprint and then asks whether the client has any questions or comments. The response is generally favorable: although the application does not yet do much, it is clear that some core functionality is in place at a very early stage.

Petra suggests that their next tasks will include formatting the messages sent to a room. She also mentions David’s idea of a content filter. The client appears very receptive to this idea and requests that the team make this a high priority for the coming sprint.

At the end of the meeting, the client leaves and the team stays back to hold their sprint retrospective.

Sprint retrospective

At the end of the sprint, the team convenes to discuss progress over the week. All team members are present and answer the following questions:

Image What went well?

Image What went badly?

Image Are there any parts of the process that we need to change?

Image Were any new things done in the sprint that we need to keep?

Image Were there any surprises discovered over the course of the sprint?

The aim is to generate a list of actionable items to prioritize and take forward. As usual, the outcome of this meeting is not to generate a lot of discussion without tangible action.

What went well?

The team members are assembled in a suitable meeting space and work through the list of questions, one at a time. They start with what went well during the sprint.

STEVE: So, what did everyone think went well in this first sprint?

PETRA: The demo was good, I think. It might not have been exactly what the customer was expecting, but I think it was positive.

TOM: Absolutely, I agree. I think that they were realistic enough to know that this isn’t a finished product, or even a first release, but that there was tangible value in what we had produced in just a short space of time.

DIANNE: They turned up, provided constructive feedback and—even at such an early stage—changed our direction.

PETRA: Yes, I think that’s worthy of a separate entry here. David, your idea for a content filter was very well received, and they have increased its priority so that we will be working on that in the next sprint.

DAVID: I’m glad that they were so clearly enthused by what we were doing. We just need to manage their expectations a little: I wouldn’t want them to oversell our work so far.

STEVE: What else went well?

DIANNE: I think that the code is in good shape. Even though there is only a small amount of code, what is there will certainly provide a good foundation for future work.

DAVID: Thanks—I think collaboration between Dianne, Steve, and myself really helped with that. If not for their guidance throughout the process, the code would already contain some significant technical debt.

STEVE: Okay, I’ll mark both of those down. Anything else?

The room falls silent, so Steve marks the following points on the whiteboard and moves on to the next question.

Image Demonstration was good, even if there was little to show at this stage.

Image Client was present at demo and gave good-quality feedback.

Image Code seems to be in good shape.

What went badly?

The next question for the team to answer is what went badly during the sprint.

STEVE: Be honest but realistic here: what went badly during the sprint?

TOM: I think I was underutilized this sprint. I realize there’s not a huge amount that I could be doing to contribute at this early stage, but there was very little to test, and what there was came in waves.

PETRA: Okay—that’s very valuable. What’s the root cause here?

STEVE: I think it’s because David was the only developer actively working on stories, while Dianne and I prepared some architectural designs for another project.

PETRA: Will this be an ongoing problem, or can we remedy this?

DIANNE: This is to be noted more in the “things to change” section, but Steve and I are going to become less like chickens and more like pigs from here on with the Proseware project.

TOM: Remind me what that means again, please.

STEVE: Basically, we’ve been contributing to Proseware but not fully committed resources as yet. We were needed elsewhere as another project reached maintenance mode.

DIANNE: Indeed. We’ll be able to write stories in subsequent sprints, which will give you a steady stream of new functionality to test, rather than the one or two flurries of activity.

TOM: Sounds good to me.

STEVE: Anything else go badly?

PETRA: Obviously, the problem with the projector at the start of the demonstration was not good.

DAVID: Yes, I’m really not sure what happened there! It was embarrassing, but I think we recovered well.

DIANNE: Absolutely, we recovered well. But we need to be able to prevent this from happening again.

STEVE: I think the problem was simply a lack of ample preparation time. Just as in all other environments, we need to integrate early and often! In the future, we should take half an hour before the demonstration to run through what we will be presenting in the meeting room, using the projector. The client will forgive that sort of error once, but any repeat will look amateurish.

Steve marks down the following items on the whiteboard:

Image QA was underutilized throughout the sprint.

Image Demonstration was almost disastrous because of environmental issues.

STEVE: Anything else?

All team members shake their heads to indicate no, and Steve proceeds.

Things to change?

Agile processes are very malleable, and teams should take the opportunity to reflect on whether the process is working for them—or against them. Making actionable items out of the things to change about the process, work environment, or other practices is an excellent way to improve the way a team works.

STEVE: Right, we’ve already driven out some changes with things that went badly. First, Dianne and myself will have to be more available to the project in this sprint. Second, we need to take an extra half-hour to prepare for the demo in the meeting room. Anything else?

The room is quiet.

STEVE: Okay—is the daily stand-up meeting working?

TOM: Actually, it isn’t—I wasn’t there twice during this sprint because I hit traffic on the way in and couldn’t make it, remember?

DIANNE: Of course—I completely forgot about that.

STEVE: Would it be better at 9:30 A.M.?

TOM: I think so. It’s just too difficult for me to guarantee that I can be here at 9:00 A.M..

PETRA: I’ll inform management that we should look into flexible working hours, Tom.

TOM: Thanks—that would be great.

DAVID: I think we need to do the demo less often. The client expressed surprise that they had to be here every week and also that we were showing them a small amount of functionality. Why not tackle both issues at the same time by demonstrating once every fortnight, instead?

STEVE: That’s a good idea—definitely worth looking in to. However, I think the demo is a great motivator for delivery. I would like to retain the weekly demonstration, but what do you think about limiting the demos for the client to every other week, with internal demos in between? We don’t need to involve management, just this team.

PETRA: I think that would work. It would allow us to perfect the demo process and show off more functionality to the client at the same time.

David looks happy, and Steve marks the following on the whiteboard:

Image Dianne and Steve to commit to the Proseware project and focus on story delivery.

Image Demonstrate once a fortnight to the client.

Image Demonstrate in between just to the team.

Image Take an extra half-hour before the demo to run through the agenda and check that everything is working.

Things to keep?

Sometimes, the best course of action is inaction—in other words, making a note of something positive that a team does but that is not yet habitual. This is the topic of the “things to keep” question.

STEVE: What about things to keep? Is there anything that we took initiative on that we should keep doing?

Everyone remains quiet.

STEVE: Okay, if anyone thinks of anything after this meeting, let me know and we’ll create an action item.

Surprises?

Almost every sprint will have revealed some surprises. The team could have discovered an antiquated process that needs updating, a requirement that wasn’t properly captured, or a piece of software that suddenly stopped working. The “surprises” section of the sprint retrospective aims to capture all of these items so that they cannot be classed as surprises in the future.

STEVE: How about surprises?

DAVID: I was quite surprised that they liked my content filter idea!

DIANNE: I think it makes great sense, to be honest. They’re targeting Proseware at a certain demographic, and it is likely to represent a lot of value to them.

PETRA: I agree, it was a great idea. Perhaps that’s something that we should keep doing: keep thinking of ideas.

Steve writes it down in the appropriate column on the whiteboard.

TOM: It surprised me that Steve and Dianne weren’t working on this project full time.

PETRA: Okay, anyone else?

DIANNE: It surprised me, too. I was under the impression that I was to be a full-time part of Proseware, but Steve and I needed to put out a fire on another project.

STEVE: We should get some kind of agreement from management that someone else will pick that up next sprint, rather than us.

DIANNE: Good plan.

Steve notes this down on the whiteboard.

STEVE: Okay, thanks guys. A good sprint overall and great start to this project. Let’s keep it up.

PETRA: I agree. Let’s make the changes agreed in this retrospective and ensure that we continue on this same path next week.

The meeting ends and the team departs.

Summary

The first sprint has been a qualified success for the team. Although not everything has gone according to plan, the team has gathered some valuable feedback in a short amount of time. This is a key part of any Agile process: the constructive criticism required to take corrective action is always nearby.

In the next chapter, the team continues working on sprint two and carries forward the stories that have not yet been implemented.