Let's Code Dimdwarf
Shows the development of a highly complex distributed application server using TDD. The project’s implementation was well under way when the Let’s Code series started (7000 LOC production code, 9000 LOC test code), so this series captures the evolution of an already substantial codebase.
- Project website: http://dimdwarf.sourceforge.net
- Project repo: https://github.com/luontola/dimdwarf
- Episodes74
- Total length32 h 30 min
- Latest episode2011-10-08
Episodes
| Introduction | ||
|---|---|---|
| Let's Code Dimdwarf #1: Introducing the Project | MP4 | |
I'm starting a new screencast series Let's Code, where I will be recording myself developing some open source projects. This was inspired by James Shore's Let's Play TDD series and I will try doing something similar. My goal is not to teach the basics of how to do TDD, but to show how one developer does it - in the hope that something can be learned from it. Each episode will be about 25 minutes long ("one pomodoro") and I will try to release a new episode every couple of days, but no promises about that. This is the first episode of the series. Since I'm starting with an existing code base, it takes some time explain the architecture of the project, so this episode does not yet include coding, but watching it is recommended to understand the following episodes. Implementation will start in the second episode. If you want to see some code quickly, then episode #5 may be a good place to start. There we start writing some isolated utility classes for asynchronous tests and it doesn't require knowledge of Dimdwarf's architecture (though in my opinion those episodes dealing with Dimdwarf's features are more interesting). |
||
| Logging Out | ||
| Let's Code Dimdwarf #2: Let the Coding Begin | MP4 | |
Here is the second episode of the new Let's Code series. We get started with implementing the logging out feature of the system, implementing it step-by-step as driven by our end-to-end tests. The progress is still quite slow, as it takes some time to understand what needs to be done next and to get into the flow. We anyways manage to implement support for one new network message type and get a good start on writing the next test. |
||
| Let's Code Dimdwarf #3: Responding to Logout | MP4 | |
Implementing logout continues and we get very close to completing the feature. Our tests are encountering problems with asynchrony, so in a later episode we will create some utility classes to help writing those tests. |
||
| Let's Code Dimdwarf #4: Tidying Logout Tests | MP4 | |
Logging out is now working, so it's time to improve the readability of our tests. |
||
| Asynchronous Testing Tools | ||
| Let's Code Dimdwarf #5: Asynchronous Testing Tools | MP4 | |
In order to easier test our asynchronous system, we need some utility classes which take care of the concurrency issues. In this episode we begin writing them. This episode can be a good "first episode" for someone evaluating his interest in this show, because here we are writing code which does not require being familiar with the system's architecture. If you like this episode, then you might want to later watch Let's Code Dimdwarf #1, where the current project and its architecture are explained, but where no code is yet written; the coding starts in episode #2. |
||
| Let's Code Dimdwarf #6: Tumbling with Exception Messages | MP4 | |
We try to integrate with the Hamcrest library and make our utility class produce useful exception messages. I haven't written Hamcrest matchers before, so it's not clear that how to do it. |
||
| Let's Code Dimdwarf #7: Growing a Fluent API | MP4 | |
Let's Code is back after a short pause due to the Software Craftsmanship 2010 conference earlier this week. In the concluding talk of the conference there was a fitting quote from The Mythical Man-Month, chapter 1: The Joys of the Craft In this episode we refactor our test code by extracting a fluent API out of it, to make the test code more readable. There is some unnecessary work in re-inventing wheels, because of not being familiar with what features JUnit and Hamcrest already provide. |
||
| Let's Code Dimdwarf #8: Escape from Refactoring Hell | MP4 | |
We finally figure out what was wrong in the code and manage to clean up the mess which had piled up during the last hour or so. The first draft of the asynchronous testing tool is now ready and in the following episodes we will be making it reusable. |
||
| Let's Code Dimdwarf #9: Second Asynchronous Use Case | MP4 | |
In order to get a reusable utility class for writing asynchronous tests, we re-implement it for another use case. After that it will be possible to extract the common parts into a reusable class. |
||
| Let's Code Dimdwarf #10: Add Synchronization | MP4 | |
Now that ByteSink's synchronous tests are passing, it's time to implement the asynchronous behaviour. Unlike with EventSink, this time synchronization needs to be done manually. |
||
| RECAP: Let's Code Dimdwarf #1-10 | MP4 | |
Recap of episodes 1 to 10, at 10 times the normal speed, with commentary. Watching this recap should make it easier to follow what is happening in the following episodes, even if you haven't had time to watch the previous episodes. |
||
| Let's Code Dimdwarf #11: Reuse via Deduplication | MP4 | |
We move towards a reusable utility class by eliminating the duplication between the existing classes for two different use cases. |
||
| Let's Code Dimdwarf #12: Right Refactoring Path | MP4 | |
Sometimes the rights steps to a refactoring are on a long and narrow path. |
||
| Let's Code Dimdwarf #13: Assertion Error Messages | MP4 | |
Testing error messages requires manual verification plus automatically verifying just the important parts. |
||
| Let's Code Dimdwarf #14: Covering All Corners | MP4 | |
When I go through things which I'm about to commit, often I find out that I'm not yet ready to commit. P.S. After a couple of productive evenings, I've piled up unedited recordings up to episode 30. So at least for now I'll speed up my release pace to reduce the WIP. |
||
| Let's Code Dimdwarf #15: Matcher Mismatch | MP4 | |
Before removing the duplication between two classes or methods, they should be made as similar as possible. There is a small mismatch in what the ByteSink and EventSink matchers match. |
||
| Let's Code Dimdwarf #16: Valuing Ambiguity | MP4 | |
If you don't right now know how to do something, embrace the ambiguity and leave it for later. |
||
| Single Responsibility Principle in Network Tests | ||
| Let's Code Dimdwarf #17: Proof of the Pudding | MP4 | |
The proof of a library is in using it. |
||
| Let's Code Dimdwarf #18: Revenge of the Hack | MP4 | |
When you take shortcuts in implementing things, it's important to always remember them (not realistic) or at least make them fail fast, so that you would be reminded about them when they bite you. My IoHandler was not fail-fast; it had a pointless null check. |
||
| Let's Code Dimdwarf #19: Close of the Split | MP4 | |
Coding with backspace is fun. The last half of splitting NetworkSpec requires mostly just removing the excess parts. |
||
| Return of the AsynchronousSink | ||
| Let's Code Dimdwarf #20: Make It Right | MP4 | |
A new look at refactoring AsynchronousSink. Earlier we made it work; now we try making it right. |
||
| RECAP: Let's Code Dimdwarf #11-20 | MP4 | |
Recap of episodes 11 to 20, at 10 times the normal speed. |
||
| Let's Code Dimdwarf #21: Ambiguity Paid Off | MP4 | |
The ambiguity left remaining in episode 16 has now been cleared. This ends the current undertaking and in the next episode will begin the work on supporting more than one client. |
||
| From One to Many Clients | ||
| Let's Code Dimdwarf #22: Preparatory Refactoring | MP4 | |
This episode starts a new arc - implementing support for more than one concurrent client. First we need a reproducable, failing end-to-end test. Also before being able to add support for multiple clients, some preparatory refactoring1 is needed: wrap the messages from clients into a class, to which we can then easily add a client session handle. 1 The word prefactoring has already another meaning, so maybe this is "pre-refactoring"? Do you have better ideas for a word? |
||
| Let's Code Dimdwarf #23: Refactoring Driven by End-to-End Tests | MP4 | |
Between each step of our refactoring, all our unit tests pass, but the end-to-end tests don't. This situtation is possible when the components and their unit tests are so well decoupled (due to using message passing) that changing one component does not affect other components - until they are plugged together. We use the end-to-end tests to drive the refactoring, by fixing components in the order that they fail, until the refactoring has been done to all of them. |
||
| Let's Code Dimdwarf #24: Merging Classes for Higher Cohesion | MP4 | |
When a responsibility of doing something is spread over many classes, it becomes harder to understand and modify. In such a situation the responsibilities should be moved or classes merged, so that all the handling of a responsibility is in one highly cohesive place. |
||
| Let's Code Dimdwarf #25: Return to Sender | MP4 | |
| Let's Code Dimdwarf #26: Avoid Rushing | MP4 | |
The temptation to make the end-to-end test pass is high, but it's better to slow down and first write some unit tests for the feature. Otherwise the unit test coverage, which is the basis for enabling refactoring, would not be complete. Maybe a rule of thumb would be: "If end-to-end tests fail, but unit tests pass, you need more unit tests." |
||
| Let's Code Dimdwarf #27: Extracting ClientRunner | MP4 | |
To be able to write a unit tests for connecting multiple clients, a helper class needs to be extracted from the existing test code. |
||
| Let's Code Dimdwarf #28: Test the Test | MP4 | |
Earlier we had implemented a feature, but the test had a bug. Now that the test has been fixed, there is a temptation to continue when we see it pass, but that would be dangerous. It's better to revert the implementation and see the fixed test fail, to make sure that the test is working. |
||
| Let's Code Dimdwarf #29: Authenticating Multiple Clients | MP4 | |
The last thing remaining to support multiple clients is to update the authentication layer. |
||
| Let's Code Dimdwarf #30: Thoughts on YAGNI | MP4 | |
Support for multiple clients has now been implemented. When making this system, initially I made it work on just one client, because I knew that adding features later to a simple codebase is easy and then your understanding of the problem will be better. It took only a bit over 4 hours to add support for more than one client - surely not much longer than if I had built it in already in the beginning. |
||
| RECAP: Let's Code Dimdwarf #21-30 | MP4 | |
Recap of episodes 21 to 30, at 10 times the normal speed. P.S. I just got a decent microphone. The sound is now much clearer than what it used to be. Unfortunately it will take until episode 42 for the regular episodes to be recorded with this mic. |
||
| Application Loading Tests | ||
| Let's Code Dimdwarf #31: Generics Magic | MP4 | |
I did some work off-camera to make the actors and message queues type parameterized, so that a similar bug as was in episode 25 would not happen ever again. In the beginning of this episode I will explain what I did and what kinds of Java generics tricks I was able to use to minimize the boilerplate in the Guice modules. Then it's time to write unit tests for things which are covered only by end-to-end tests. |
||
| Let's Code Dimdwarf #32: Application Loading Test Names | MP4 | |
Some areas of the system are still only covered by the end-to-end tests, so they need some unit tests to enable testing them faster and to put more design pressure on them. One of these areas is application loading. I begin by choosing the names of the tests to be written, which is easy because I already know based on the existing implementation that what features it has. |
||
| Let's Code Dimdwarf #33: Extracting a Sandbox | MP4 | |
We need a reusable utility class for managing temporary directories, so we will extract one from our existing test utilities. |
||
| Let's Code Dimdwarf #34: Unique Temporary Directories | MP4 | |
When creating temporary directories in tests, a timestamp and a global variable is not unique enough. You should always assume that the tests will be run in parallel. Even when the test runner you are using does not have built-in support for running them in parallel (though it could in the future), it's possible to run the same test suite multiple times in different processes (which may happen especially on CI servers - I once had tests conflicting because of listening on a fixed network port number). |
||
| Let's Code Dimdwarf #35: Test-After | MP4 | |
I begin writing tests for application class loading. I had planned on test-driving some new code, but because I so seldom write code test-after, instead I decided to refactor the existing code and make it testable as if dealing with legacy code. |
||
| Let's Code Dimdwarf #36: Safety Net | MP4 | |
I will start by writing one unit test to cover the application loading, after which it will be safe to extract the code into its own class. |
||
| Let's Code Dimdwarf #37: Refactoring | MP4 | |
The methods were easy to move to another class when they all were static, but extracting the class does not end there - the design needs to be made right. |
||
| Let's Code Dimdwarf #38: More Refactoring | MP4 | |
Still some more cleaning up of the production and test code until they are tidy enough for us to move forward. |
||
| Let's Code Dimdwarf #39: More Test Cases | MP4 | |
P.S. Doesn't (0..*) look like a smiliey to you? :D |
||
| Let's Code Dimdwarf #40: Better Asserts | MP4 | |
Creating your own assert methods is oftentimes useful, to make the test code and the assertion messages read better. In this episode I will also write a test for class loading from JAR files. |
||
| RECAP: Let's Code Dimdwarf #31-40 | MP4 | |
Recap of episodes 31 to 40, at 10 times the normal speed. |
||
| Let's Code Dimdwarf #41: Class Loader Trouble | MP4 | |
| Manual DI Spike | ||
| Let's Code Dimdwarf #42: Without DI Frameworks | MP4 | |
I had an idea to see how the program would fare without a Dependency Injection (DI) framework such as Guice. It should be possible to use just manual DI. It might even be much simpler that way. To find out, I will do a spike. |
||
| Let's Code Dimdwarf #43: It's Alive! | MP4 | |
Now that a hard coded manual dependency injection configuration is working, it will be possible to begin refactoring it and remove all duplication, until a reusable framework or DSL emerges. |
||
| Let's Code Dimdwarf #44: Making the Dependencies Explicit | MP4 | |
The main principle of dependency injection is to make all dependencies of a module explicit. By refactoring the hard coded DI configuration so that the dependencies between modules become more explicit and the duplication more obvious, the design desired by the code begins to emerge. |
||
| Let's Code Dimdwarf #45: Layers of Indirection | MP4 | |
Next I refactor the DI configuration towards an easy-to-use API. |
||
| Let's Code Dimdwarf #46: Encapsulated API | MP4 | |
I move the generic code into its own classes and the DI configuration becomes quite orderly. |
||
| Let's Code Dimdwarf #47: Manual DI vs. Guice | MP4 | |
The spike for manual dependency injection configuration is nearing its end and it's a good time to compare it with the original Guice-specific code which was needed to do the same thing. The infrastructure code is 50 lines of Scala for manual DI, vs. over 120 lines of Java code for Guice-based DI (which also includes lots of complex reflection). There is slightly more configuration code with manual DI, but it's also more explicit and it's easier to find out what is happening. |
||
| Command Line Parsing | ||
| Let's Code Dimdwarf #48: Command Line Parsing | MP4 | |
I begin using the args4j library for parsing command line arguments. The tests I write are mostly learning tests. |
||
| Session Messages | ||
| Let's Code Dimdwarf #49: Session Messages Roadmap | MP4 | |
Here starts the work on implementing the sending and receiving of session messages between the client and server. Implementing that will drive the design of the overall architecture quite far, because it will among other things require executing application code in worker threads and committing transactions. As usual, we start with a failing end-to-end test. |
||
| Let's Code Dimdwarf #50: Decoding Session Messages | MP4 | |
The end-to-end tests proved to be useful in catching a mistake in understanding the requirements which caused the unit tests to specify the wrong kind of encoding for session messages. |
||
| RECAP: Let's Code Dimdwarf #41-50 | MP4 | |
Recap of episodes 41 to 50, at 10 times the normal speed. |
||
| Let's Code Dimdwarf #51: Walking Skeleton | MP4 | |
My strategy for implementing session messages - and the half a dozen still nonexistent components which it depends on - is to start with a walking skeleton. This is the approach recommended in the GOOS book which I wrote about in Design for Integrability. In this episode I will explain my strategy and implement the outline of the walking skeleton. Actually this is the second time during this project that I've used a walking skeleton. The first walking skeleton included receiving a login request and responding to it with a login failure. The first skeleton let me put in place these components: system startup, application loading, networking, authentication and the controller-actor architecture. This second skeleton will produce: unique timestamps, session IDs, worker thread handling, application APIs to listen for and send session messages, executing tasks in application code, sending session messages and committing transactions. P.S. I wonder whether these may technically be called the first and second walking skeleton, since they don't include all main architectural components. This system has lots of essential complexity, so I prefer taking as small steps as possible. You could say that at first I implemented the legs of the walking skeleton (the controller-actor achitecture), now I implement the hands of the walking skeleton (task execution), and later I will implement the head of the walking skeleton (persistence). |
||
| Let's Code Dimdwarf #52: Progressive Deepening | MP4 | |
In this episode I get the session message roundtrip working with the simplest possible implementation - all in one method, without any layering. Of course this cannot work in production, but it lets us get started with something that works. After that it's time to do progressive deepening - introduce more layers into the software and push the functionality to the layer where it belongs to - and also do progressive widening - flesh out those layers with more functionality and production-ready quality (see Craftsmanship and Ethics at 18-21 min). |
||
| Let's Code Dimdwarf #53: Fixing Encapsulation | MP4 | |
I will start deepening and widening the design by first fixing the encapsulation issue with the network layer - the |
||
| Let's Code Dimdwarf #54: Timestamp Ordering | MP4 | |
To be able to implement session IDs, guaranteed unique values will be needed - also in a clustered environment. That can be solved by creating unique timestamps based on the paper Time, Clocks, and the Ordering of Events in a Distributed System (though first I'll implement simpler timestamps). I'll start by implementing the ordering of the timestamps, for which parameterized tests are useful. |
||
| Let's Code Dimdwarf #55: Pretty Printing | MP4 | |
It's always good to be extra careful when dealing with the maximum and minimum values of an integer. After solving that bug in the tests, I'll improve the toString method. |
||
| Let's Code Dimdwarf #56: Timestamp Clock | MP4 | |
There needs to be a factory for generating unique timestamps. |
||
| Let's Code Dimdwarf #57: Thread-Safe Clock | MP4 | |
The factory for generating unique timestamps must be thread-safe. So I will write a test for thread-safeness and start using an AtomicReference. |
||
| Let's Code Dimdwarf #58: Session IDs | MP4 | |
Once we have timestamps, generating session IDs is trivial (but anyways worth writing a test for). Then the real thing is mapping those IDs to sessions, which is far from trivial. |
||
| Let's Code Dimdwarf #59: Session ID Mapping | MP4 | |
When you would like to expose some internal state of a class just to be able to write tests for it, it's usually better to extract that internal thing into its own class which can then be tested through its public interface. Thus I will start writing a new class for keeping track of the mapping between sessions and session IDs. |
||
| Let's Code Dimdwarf #60: Smells Like State Machine | MP4 | |
As disconnecting sessions comes to play, the code starts to inhibit code smells - conditional logic. It would be possible to get rid of those smells with a state machine and the state pattern, but the smells are still quite faint, so let's implement still one more step before refactoring, so that it will be obvious that what shape the code should take. |
||
| Let's Code Dimdwarf #61: State Machine | MP4 | |
The code starts to be ripe enough to be refactored into a state machine. |
||
| Let's Code Dimdwarf #62: Refactoring Domino | MP4 | |
It's curious how sometimes with TDD the refactoring steps follow each other almost automatically, like dominoes. You make one small change and then just keep on fixing test failures until it all works again and the refactoring is done. |
||
| Let's Code Dimdwarf #63: You Will Be Assimilated | MP4 | |
The ClientSessions class will absorb most of the logic from NetworkController, leaving the controller just wiring things together and delegating all work to others. Let's first move the authentication logic. |
||
| Let's Code Dimdwarf #64: Asynchronous State Machine | MP4 | |
When a collaborator can work both synchronously and asynchronously, it complicates things as the caller needs to make less assumptions. In our unit tests the authenticator is a synchronous fake (it keeps the tests simpler), but the production authenticator is asynchronous (because it does blocking operations). The state machine needs to be updated to work with both synchronous and asynchronous collaborators. |
||
| Let's Code Dimdwarf #65: Feature Complete | MP4 | |
The state machine has now been implemented and most logic has been moved from the controller to the ClientSessions state machine, leaving the controller focused on just simple mapping. Only some refactoring remains. |
||
| Let's Code Dimdwarf #66: State Machine DSL | MP4 | |
| Let's Code Dimdwarf #67: Hide SessionHandle | MP4 | |
The |
||
| Let's Code Dimdwarf #68: Session API | MP4 | |
Deepening the design of our walking skeleton by creating the application API for sending and receiving session messages. |
||
| Let's Code Dimdwarf #69: Commit Requests | MP4 | |
Deepening the design of our walking skeleton by introducing asynchronous messages for transaction commit requests. |
||