Johannes Brodwall's Musings on Software Architecture and Programming

Let’s reinvent more wheels!

When I learned math in elementary school, I would reach for my calculator. But my father stopped me: “You only get to use the calculator when you can do math without it.” As you can imagine, at the time I though this was unfair and unreasonable, but later I have discovered what advantage it is to understand the basics before you reach for a powerful tool.

Many developers are focused on what fancy framework they will learn next. Or what programming language might solve all their problems. Before we reach for the tools, it’s useful to learn how they really work. My motto is: “I will not use a framework I would be unable to recreate.” It is of course, too time consuming to create a framework as complete as many of those available, but I should at least be able to solve all the normal cases myself.

Having recreated the behavior of a framework means that I will understand how the framework is implemented. I get better intuition about how to use the framework, I understand more quickly what the problem might be when something doesn’t work, and last, but not least: I understand when the framework is harming me more than it’s helping me.

An example I have enjoyed using is to create a web application in Java without framework. I may use a simple domain like creating an address book where you can register your contacts and search for them. In honor of my C#-friends, I have solved the same task in C#: How to make a web application without MVC, without ASP.NET or even without IIS.

Test-driven development is an essential tool for me to think clearly. I’ve made an exception from the calculator rule above and used a few testing libraries: SimpleBrowser.WebDriver, FluentAssertions and NUnit. This test demonstrates the behavior that I want from the application when I’m done:

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

[Test]

publicvoidShouldFindSavedPerson()

{

// Start a web server INSIDE THE TEST :-D

varserver=newMy.Application.WebServer();

server.Start();

varbrowser=newSimpleBrowser.WebDriver.SimpleBrowserDriver();

browser.Url=server.BaseUrl;

// Navigate to the "add contact" page

browser.FindElement(By.LinkText("Add contact")).Click();

// Add a new contact

browser.FindElement(By.Name("fullName")).SendKeys("Darth Vader");

browser.FindElement(By.Name("address")).SendKeys("Death Star");

browser.FindElement(By.Name("saveContact")).Submit();

// Navigate to the "find contact" page

browser.FindElement(By.LinkText("Find contact")).Click();

// Execute some queries:

browser.FindElement(By.Name("nameQuery")).SendKeys("vader");

browser.FindElement(By.Name("nameQuery")).Submit();

browser.FindElement(By.CssSelector("#contacts li")).Text

.Should().Be("Darth Vader (Death Star)");

browser.FindElement(By.Name("nameQuery")).SendKeys("anakin");

browser.FindElement(By.Name("nameQuery")).Submit();

browser.FindElements(By.CssSelector("#contacts li"))

.Should().BeEmpty();

}

I add an empty class for My.Application.WebServer and the test will fail at the line browser.Url = server.BaseUrl as there is not real server.

To implement the server, I’m using a cute small class which is part of the .NET core library: System.Net.HttpListener. Here are the essentials:

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

classWebServer

{

publicvoidStart()

{

varlistener=newSystem.Net.HttpListener();

listener.Prefixes.Add(BaseUrl);

listener.Start();

newThread(HttpThread).Start(listener);

}

privatevoidHttpThread(objectlistenerObj)

{

HttpListener listener=(HttpListener)listenerObj;

while(true)

{

varcontext=listener.GetContext();

using(context.Response)

{

}

}

}

}

Running the test again, I get one step further. This time, I am told that the test can’t find the link to “Add contact”. No big surprise, as I’m not serving any HTML! A small change in the WebServer code will fix this:

C#

1

2

3

4

5

varcontext=listener.GetContext();

using(context.Response)

{

newAddressBookController().Service(context);

}

Then we just have to create a simple implementation for AddressBookController.Service:

C#

1

2

3

4

5

6

7

8

9

10

11

12

classAddressBookController

{

internalvoidService(HttpListenerContext context)

{

varhtml="<html>"+

"<p><a href='/contact/create'>Add contact</a></p>"+

"<p><a href='/contact/'>Find contact</a></p>"+

"</html>";

varbuffer=Encoding.UTF8.GetBytes(html);

context.Response.OutputStream.Write(buffer,0,buffer.Length);

}

}

Again, the test will get one step further. Now we can see that the main page is presented with the links “Add contact” and “Find contact“. After Click()ing “Add contact” the test will of course fail to find the field fullName as we have not created the form yet. The method HandleGetRequest inspects the URL to determine which page should be displayed:

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

internalvoidService(HttpListenerContext context)

{

varhtml=HandleGetRequest(context.Request);

varbuffer=Encoding.UTF8.GetBytes(html);

context.Response.OutputStream.Write(buffer,0,buffer.Length);

}

privatestringHandleGetRequest(HttpListenerRequest request)

{

if(request.Url.LocalPath=="/contact/create")

{

return"<html>"+

"<form method='post' action='/contact/create'>"+

"<p><input type='text' name='fullName'/></p>"+

"<p><input type='text' name='address'/></p>"+

"<p><input type='submit' name='saveContact' value='Save'/></p>"+

"</form>"+

"</html>";

}

else

{

// As before

}

}

We’re almost done saving contacts. The test fails to find the link “Find contact” after submitting the form. The method Service must be modified to handle POST requests and perform a redirect back to the menu:

All that remains to turn this into a real application is to use a real database and correct the obvious security vulnerability when we display contacts. The AddressBookWebServer could have a Main method to enable you to run the code. But I’ll leave these issues as an exercise to you, dear reader.

This article has showed how HTTP really works and how frameworks like ASP.NET MVC work behind the curtains. There are many details that we’re glad that the framework can fix for us, like the character encoding and reading the contents of a POST request. And there are many things that turn out to be not as hard as you’d think, like a real “redirect-on-post” pattern. In more than one project, I’ve realized that after spending a few days understanding the underlying technology, I could deliver the project much better and faster without the “obvious”, popular frameworks that everyone recommend that you use.

Did I reinvent the wheel in this article? You could argue that I did, but let me stretch the metaphor of “reinventing the wheel” as far as possible:

My experience is that many “cars” today have misaligned wheels where the axel isn’t mounted in the center. Maybe the wheel was poorly constructed, or maybe the car was just assembled wrong. Maybe we notice that the car is bouncing because two of the wheels have a misaligned axel. And then we spend a lot of work trying to adjust these wheels to synchronize the bouncing. Finally we publish articles about the nice even undulations of our car after aligning the errors in the wheels.

If we have some experience contructing one or two “wheels”, it’s possible that we’re able to identify the real problems with the “wheels” we were given, so we can determine which “wheels” are good and which “wheels” are bad. Not to mention: We may learn how to use them properly.

Reinvent wheels you don’t understand, don’t use a framework you couldn’t have made yourself and don’t use a calculatore before you understand math.

True, Dileepa. I’ve been in this situation with Java. It had its upsides (very easy for me to get good test coverage) and downsides. The downsides are greater the less you know. If you create your own mini-framework a few times (and throw it away), you eventually get pretty good at it.