Saturday
Nov102012

Migrating to Git, Part 3: Moving your code to Git

This is the third and final part in a three part series about migrating to Git. I recommend reading part one and part two if you haven't already. Migrating to Git is worth it, but it's not a trivial decision.

Migrating to Git, Part 1: Advantages

Migrating to Git, Part 2: Prerequisites

If you're ready to proceed with a migration to Git, congratulations! You're about to start using one of the best development tools available today.

Pre-migration steps

1. Do you need to maintain commit history?

If you're moving from SVN to Git, the migration to Git is greatly simplified if you don't need to retain revision history. Otherwise, you'll need to explore SubGit or git-svn.

Some of the steps required for git-svn are:

  • Retrieve a list of SVN committers and transform them to Git users (name, e-mail)
  • Clone the SVN repo using git-svn
  • Convert svn:ignore to .gitignore
  • Push the SVN repo to a bare Git repo
  • Rename "trunk" to "master"
  • Clean up branches and tags

These aren't the most time consuming tasks, but they're not quick either. If you're unconcerned about porting over history, I highly recommend skipping most of the steps above. We simply kept historical data in FishEye and instructed developers to query it if they had any questions about pre-Git revisions. Other version control systems will have their own tools to help with the migration, but convering each different VCS is beyond the scope of this series.

2a. Install Git and other dependencies on the central Git server

At a bare minimum you'll need to install Git. I'm assuming you'll be using a Linux server, so this is an easy step. You may also need to install an up-to-date version of Java or Ruby (depending on what repository management tool you choose below).

2b. Set up a repository management tool

It's easy to initially set up Git by hand, but it becomes more and more of a burden to maintain a central Git repository without an easy-to-use management tool. Popular repository management tools include:

My personal favourite is GitHub Enterprise, but the cost is hard to justify. GitHub Enterprise would cost our team of over 20 developers $10,000 per year in licensing. Ouch.

We're using Atlassian Stash at the moment instead, and while it's frustringly bare-bones, it provides the bare minimum of what we need and is much cheaper. We're banking on it gaining features as it matures.

Kando adds workflow functionality similar to ClearCase. Depending on your existing workflow it may be too prescriptive for your needs or it may be exactly what you need.

2c. Select and install a Git client and additional developer tools

msysgit is a no brainer for those in a Windows environment. This should be installed on all developer workstations before the migration, and each developer should create a local repository as a test to make sure it's set up properly. This will help to avoid any day-of-migration surprises that will kill velocity.

Other helpful tools include:

Our developers are in a Windows environment, and we use Beyond Compare, TortoiseGit, and bash. For the most part we're happy, but I've also heard good things about SmartGit. I've also heard amazing things about Tower, and I would not hesitate to switch to it if our developers used OSX.

2d. Code review and other tools

It's worth the time to stop and identify areas of weakness in your current process. Opportunities for improvement may exist with support from the right choice of tools. This is an especially good opportunity to identify a solid code review tool, which will also force the team to decide on a post-commit or pre-commit workflow. 

  • Post-commit workflow: Review code after it has been pushed and merged to the development branch.
  • Pre-commit workflow: Review code before it is merged. Pull requests rather than pushing are an example of a pre-commit workflow.

Some decent code review tools for Git include:

  • Barkeep (post-commit workflow)
  • Gerritt (pre-commit workflow)
  • Crucible (post-commit workflow)
  • Pull requests in your repository management tool

As of version 1.3 Stash supports pull requests, which are an excellent choice for code reviews. GitHub Enterprise and GITLAB also support pull requests, so it's not always necessary to augment your Git management tool with an external review tool. You should evaluate each and decide which works the best for your team. One thing to keep in mind is that pull requests only help to review code that has yet to be merged.

3. SSH authentication

Developers will most likely authenticate with remote Git repositories using SSH. This will require all developers to generate a private/public SSH key pair and add their public key to the Git server (in the authorized_keys file or adding the public key to the Git repository management tool). Using a repository management tool like GitHub Enterprise will make maintaining SSH keys a much more streamlined process rather than manually adding them to the authorized_keys file.

4. Inventory all SVN-related scripts

Custom scripts with SVN references should be identified as these will obviously break after the migration. This is typically the most time consuming task if a lot of SVN scripts and hooks exist as part of the current development workflow.

Migration steps

The migration itself is fairly straightforward if you aren't preserving SVN history. If you need to port history this list will look slightly more complicated and you should look into using SubGit.

1. Design a new repository structure

We had a single large SVN repository with every single application in our organization. Rather than port our monolithic SVN repository to Git, we decided to break it into smaller repositories that better fit with the conceptual and logical view of our applications.

We created a separate repository for our:

  • Application code
  • Application configuration artefacts
  • Back-end processing jobs
  • Static web resources and Apache configuration

2. Create the new Git repositories locally

We grabbed the latest version of our code from SVN and split our monolithic repository into four separate local folders. We had to clean the SVN kruft from each folder, which was incredibly fun. After all of this was done we initialized each folder as a new Git repository.

3. Add content to Git before building applications

We added all of our files to Git first before performing a full-build or even importing our applications into an IDE. This made it easy to create our .gitignore files, because we performed a commit of what's in SVN and nothing else (such as compiled resources or IDE project configuration files).

4. Build applications and configure .gitignore

Perform a full build of each application after you have all of your files committed to Git. You'll immediately be able to identify all the generated build artefacts that shouldn't be versioned. These artefacts can safely be added to .gitignore. Remember to add and commit your .gitignore file(s) too!

5. Update tooling and scripts to switch from SVN to Git

This is arguably one of the most time consuming tasks of the migration. For instance, Jenkins/Hudson users will need to update all of their scripts to pull from Git instead of SVN. All custom SVN-related scripts will need to be updated.

6. Create central repositories and push local commits

If you're using a tool like Stash or GitHub Enterprise, you'll manage the creation of new repositories through the tool's UI. Otherwise, you will need to create bare repositories on the Git server.

7. Every developer should clone, commit, push, pull

It's fairly important for all developers to clone the repo(s) right away and push code to identify any issues that may have cropped up during the migration. It's recommended that the person leading the migration create a throw-away branch and instruct all developers to check it out and perform a few smoke-test changes.

At the very least the entire team should be able to:

  • Build their environments locally
  • Switch branches
  • Make a small throw-away change
  • Push their change
  • Pull everyone else's changes
  • Switch branches back to the master (or release) branch

If you're feeling brave, you can even set things up so a change forces merge conflicts. This will get the team used to using the difftool and mergetool commands.

8. Application(s) should be built in a test environment and shaken-down

Once developers are fairly happy that they're set up and ready to go, the version of code now on the central repositories should be built and deployed to test environments. Presumably this will be done using a continuous integration tool and the fancy new Git scripts that were updated during pre-migration.

Retrospective

Most of the issues that come up during post-migration are related to the rationalization for switching to Git in the first place. During our transition we experienced very few technical issues with our code, but we experienced the natural frustration of learning a new tool and a new development process all at the same time.

Be prepared to deal with:

  • Build script failures after the modification of SVN scripts to support Git
  • Learning curve of using Git for the first time
  • Resistance from fans of the status quo
  • Questions from management as to the return on investment

Is it worth it? Absolutely. After the intial learning curve with Git, our SPM metric (smiles per minute) has gone up by at least one million percent.

Saturday
Nov102012

Migrating to Git, Part 2: Prerequisites

This is the second part of a three part series about migrating to Git. If you haven't read part one and are unfamiliar with the advantages of Git, you should check it out.

Migrating to Git, Part 1: Advantages

If you already understand the benefits of Git, or have already read part one, welcome to part two in the series! We're going to discuss certain pre-conditions of a migration to Git and make sure your team is ready for the transition.

Prerequisites

Before migrating to Git, you should make sure your team is ready to migrate. Changing version control systems (especially in a large team) is not a trivial undertaking, and certain technical debt should not exist in your code base to ensure a smooth transition.

Here's a rough guide to help you evaluate whether you're ready to migrate:

1. Ensure your code base is buildable and stable

If you can't perform a clean, full build of your application, you should absolutely delay your migrations plans and pay down technical debt before attempting any large scale project (let alone something is risky as migrating to a different version control system). Post-migration, the entire application will need to be completely rebuilt and regression tested. This seems like an obvious prerequisite, but not every organization performs full builds on a regular basis, especially with legacy applications. Being unable to build your application or lacking confidence in your build is a recipe for disaster.

2. Set up a continuous integration server

It's a good idea to implement a CI solution in order to get up-to-date feedback on build issues as developers begin to use Git for the first time. It's amazing the types of issues that crop up in the first few days post-migration. Most issues are minor if they're caught early enough.

3. Prepare a regression test plan or implement automated testing

Changing version control systems is quite disruptive. Prepare for a number of things to go wrong. At the very minimum, you should plan for a full regression test of your application immediately after migration and before developers begin committing code. This window should be minimized to avoid syncing nightmares.

If you don't have an automated test suite, this may be a burden to your QA team. There may be a temptation to piggy-back the Git migration onto a major release and kill two testing birds with one stone, but I caution against this as it will be hard to track down the root cause of defects.

If you work in a team that practices Kanban or Scrum, it's a good idea to plan for a loss of velocity during the migration period. This loss of productivity should be minimal if the migration is planned properly. For instance, if your team practices Scrum, it may be a good idea to consider the migration iteration zero and release the product (fully tested) at the end of the sprint. A full sprint is unnecessary, but a short sprint (of a week or less) is also a good opportunity to pay down any kind of technical debt that is a blocker of the migration to Git.

4. Plan for training

Our team attempted to train grassroots-style... and it was tough. Budget for at least a full day of training to ensure your developers are comfortable with core Git and DVCS concepts.

It also helps that developers understand the rationale for switching version control systems, as any impedance to velocity will be met with frustration. If they understand the long-term benefits, it will make the short-term frustrations easier to deal with.

5. Identify an evangelist to lead the migration and post-migration support

At least one member of the team should be very familiar with Git in order to ensure a smooth migration.

Certain questions will come up, like:

  • What's the team's branching strategy?
  • How will the team collaborate?
  • What merge strategy will be used? (Squash merges? Fast-forward merges?)
  • How often should merges take place from feature branches to a release branch?
  • Will we use sprint branches? Release branches? Or integration directly into a master branch?

It's helpful for one person to take ownership of the migration and establish a developer workflow before the migration. The workflow will evolve, but it helps to have a starting point and a rough objective. The evangelist will guide the team through the initial learning curve, and after the entire team is comfortable with Git the decision making process will naturally become more democratic (and decentralized, just like Git).

Speaking from personal experience, it's helpful (but not required) that the evangelist is not distracted with development tasks immediately after post-migration. I recommend considering them a build manager or a DevOps resource for a finite period of time post-migration to ensure capacity for support.

6. Make a list of why you're migrating to Git in the first place

This the least technical prerequisite and more of a return-on-investment analysis exercise.

It's important to come up with a justification for the migration. If you're unable to make this list and be prepared to justify it, you probably don't have someone on the team that understands the benefits of Git well enough to support the migration through the often challenging transitional period. Simply put, if you're unable to justify the expense and disruption of a VCS migration, don't bother. Wait around until you're about to create a reasonable business case and that you have specific measurable goals to revisit during the post-migration retrospective.

This list may include items such as:

  • Increased developer productivity
  • Encouraging exploration and refactoring through rapid branching
  • Reducing errors during branch merges (shaking down merges locally)
  • More naturally supporting team and release workflow
  • Supporting workers who aren't always on the network
  • Developer happiness
  • Etcetera

The final steps

If you're confident and ready to migrate to Git, check out the final part of my Migrating to Git series.

Migrating to Git, Part 3: Moving your code to Git

 

Friday
Nov092012

Migrating to Git, Part 1: Advantages

Git is unlike typical version control systems. Sure, Git is used to version files, but how is that any different than SVN or SourceSafe or ClearCase?

Git expects developers to understand how it works under the hood. It expects developers to be comfortable with the command line to get the most out of it. It provides very little in the way of convention and expects you to design your own workflow simply by using it as another tool in your arsenal. Git is not an out-of-the box solution. If you plan to transition to Git you also need to set aside time to examine your overall development process. You won't get the most out of it otherwise and will possibly experience a challenging migration.

Doesn't that sound dreamy?

Before you become discouraged, let me explain that Git is not just a version control tool. It's also a development workflow design tool. It's also a quality control tool. It's also a productivity tool.

Git is simply one of the best things that's happened to the development workflow in a decade; if you choose to master it. If you're not willing or able to put in the amount of time needed to properly learn a new tool and are simply looking for a place to dump your code, stick with your current VCS or consider migrating to something other than Git.

Three advantages of Git over SVN

SVN is a centralized version control system (CVCS), while Git is a distributed version control system (DVCS).

What's the difference between a CVCS like SVN and a DVCS like Git?

1. Stability of shared repositories

  • SVN: All developers commit work-in-progress code to shared repositories.
  • Git: Developers commit code to local repositories first. Local repositories are later synced with shared repositories. The workflow looks like this: clone -> pull -> add -> commit -> push. Until a git user executes a push, commits are kept safe and sound in their local repository.

Why does this matter?

Speed: Developers using Git commit freely as their work does not immediately impact the rest of the team. They experiment in branches, committing code or stashing changes throughout the day. Nothing is permanent and every mistake can be reversed without impacting the entire team. They can throw everything away and start from scratch if necessary. Not only that, but Git commands are blazing fast because they don't involve network traffic to a centralized server. Only fetch, pull, and push involve any network traffic. Every other command operates on the user's local repository.

Confidence: The Git workflow removes fear and builds confidence. Over time a team is more and more willing to take on work they may have deemed too scary when using a VCS like SVN. Why is this? They don't need to struggle between the lesser of two evils: not committing code, or sharing code that isn't fully tested. Git lets a developer have the best of both worlds; keeping their code in version control, and only sharing changes when they are ready to be pushed to the team. It's nice knowing that your code is safely version but that you are not permanently impacting your team if you make a mistake.

Collaboration: One may argue that collaboration is hampered because code is kept in local repositories until finished or stable. Keep in mind that nothing stops multiple developers from remoting to each other's repositories to share work-in-progress code. Other options for collaboration include pushing work in progress code to a code review repository before pushing it to a stable, public repository. Collaboration options are limited only the imagination of your team.

2. Branching

  • SVN: Developers often perform bulk commits towards the end of feature development. Work-in-progress code is often left unversioned to avoid negatively impacting the rest of the team.
  • Git: Branches are created and destroyed rapidly and locally to support new features or experimental code. Local branches are pushed to various remote repos for team collaboration. The risk of unversioned code is severely mitigated with private repositories and the ease of pushing branches to shared servers.

Why does this matter?

Integration: Developers using Git don't impact the entire team with individual commits being shared before an entire story (or feature) is stable and tested. Developers create branches and merge changes when a feature is ready for integration. Branching is possible with SVN, but it isn't used in the same fashion as in Git, because branches in SVN are expensive to create. 

Process and workflow: Git is just as much a workflow tool as it is a source control tool. Rapid branching makes working on more than one feature an incredibly flexible process. Merging in SVN is often a nail-biting experience because it's such a potentially destructive process. By contrast a developer using Git can create a new branch, merge multiple branches into it, resolve conflicts, and test locally before squashing the individual commits and integrating it with a main release branch. The worst case scenario in Git is starting over again locally without any impact to the team. The worst case scenario in SVN is corrupting the central repository and getting fired.

3. Task switching

  • SVN: Switching between branches is painful. It's essentially like copying and pasting an entire directory structure and having multiple local copies of your project.
  • Git: Git handles switching branches by simply updating the contents of a working folder with the contents of the branch you specify. There are no folders to duplicate or other steps that impede your workflow.

Why does this matter?

Productivity: Developers using Git simply commit (or stash) their work-in-progress code and checkout the alternate branch they'd like to work on. Their working directory is updated in place with the correct code and they continue to work uninterrupted. When they finish their task they simply pull -> commit -> push and then checkout the previous branch they were working on.

To be continued

Are you excited about Git? I am, and so are a lot of other developers!

If you're interested in learning more about the tasks involved in migrating to Git, please continue to read the next two instalments of my Migrating to Git series.

Migrating to Git, Part 2: Prerequisites

Migrating to Git, Part 3: Moving your code to Git

 

Monday
Jul022012

Joy at QCon New York

I recently travelled to New York City to attend QCon.

Joy isn't the typical emotion that comes to mind when one pictures enterprise software and enterprise software developers, but technical culture is changing rapidly and more people are recognizing the business value of joy.

It's an exciting time to be a software developer.

A decade ago we watched a grim-faced Peter Gibbons in the movie Office Space commiserate about his mind-numbing job of churning out Y2K patches for the cold and heartless Initech Corporation. Today we watch Mark Zuckerburg in The Social Network conquer the world in a hoodie. While Mark Zuckerburg may not be the software developer that geeks around the world aspire to become, it does signal a rapid change in popular culture.

In the past a programming job was to be endured until something better came along. Today being a talented hands-on software developer is something to aspire to. A decade ago mediocrity flourished, encouraged by heavy-handed processes and endless bureaucracy, driving frustrated developers into other roles and professions. Today engineers rapidly imagine and create everything from new businesses to entirely new business domains. The cloud, SaaS, PaaS, the App Store, the Marketplace. A decade ago businesses could afford a technological misstep. Today businesses are ruthlessly judged on their technical prowess by a generation of people who look at a magazine as a broken iPad.

Highlights

For me, the highlight of QCon was meeting so many other developers who are just as passionate about technology as I am. From the CTOs of companies like Etsy to fellow hands-on developers from Toronto, everyone I spoke with at QCon had an educated and informed opinion about the reality of developing software in 2012. Surprisingly, most of the people I met were not focused on the nitty gritty low-level details of implementing code, but instead focused on how people interact with each other and how people interact with software. Does what we build make people happy? Is the environment we work at positive? Can we build better software and be even happier in the process?

Software developers are people who build software for other people. It's amazingly simple, but so often overlooked. As a profession we spend enormous amounts of time thinking about how to optimize our processes to suit machines, but often spend too little time thinking about how to optimize our processes to suit people. The result? Many software developers find themselves working in lonely conditions with little chance to interact with the people they build for.

Great companies understand that great software comes from developers who understand, empathize, and feel connected to the people they create for. Great companies understand that great products are created by joyful people.

Great companies think alike

When software developers think of prototypical great companies, they probably think of names such as Facebook, Twitter, Etsy, and Menlo. All of these companies were represented at QCon, and all of these companies share certain cultural traits and processes that help attract some of the brightest engineers to their ranks.

What common processes or beliefs do most of these companies share?

Bootcamp.

Top companies start an employment relationship the right way, with a warm welcome that includes mentorship and training. Not only technical training, but also a gentle and welcoming introduction to an employer's culture. This welcoming is sometimes referred to as bootcamp.

Bootcamp is arguably a terrible term to use for such a positive experience. When I think of bootcamp I think of Full Metal Jacket, but bootcamp at software companies has little in common with the bootcamp envisioned by Stanley Kubrick.

Empathetic development.

The inspirational Richard Sheridan put it so eloquently.

We work in an industry full of pyromaniacs.

Firefighting. Being thrown into the fire. Burn-down. Burnout.

There's a certain macho geek swagger associated with these types of cold (or hot) analogies used in tech companies around the world. Maybe this is why so few hands-on developers are female and we're pretty much the most non-diverse profession in the world. As an industry we take pride in weeding out the weak with ruthless nerd rage. Every working developer has a memory of a failed software project and the satisfied smirk of co-workers who enjoyed watching the meltdown.

We often promote talented engineers from technical roles to management roles in order to perpetuate the cycle of machine over man. These new managers are offered little training and guidance in their new roles. We sit back and watch as they manage people the same way they managed machines and wonder why the entire organization is miserable.

Overtime. Death marches. Deadlines.

It's only a matter of time when archaic practices of the past are rendered obsolete because they simply don't make money. It's impossible for miserable businesses to compete with joyful ones. The only reason this type of culture still permeates our industry is because of how few truly joyful businesses exist.

Would you rather use Project or Basecamp? Changepoint or Tick? QuickBooks or FreshBooks?

Innovation days.

Top companies seek out innovators. There's a big difference between talking about innovation and investing in innovation. How much time do you allocate for your team to explore their own ideas? None? You're not an innovator.

The best, most disruptive, most lucrative ideas are often not in the official plan. Your clients don't want to pay for them yet. They're simply ideas swimming in the heads of your team. Not all of these ideas pay off, but some of them have turned into the most ubiquitous technologies we use today, like Google Maps and Facebook Timeline.

Experienced developers are Sherpas, not pack mules.

Most companies struggle to provide a career path for their most senior hands-on technical talent. To paraphrase David Laribee of VersionOne, many senior developers reach a point of becoming a pack mule. They become so specialized in version 8.1.4.9 of RandomTechnology that they can never escape it, and the rest of their days are spent in quiet desperation pushing around packs of commodity code.

What's the difference between a junior team member and a senior team member? Is it years of service? Of course not, it's the transition from student to teacher.

The cheerful smiles and legendary strength of the Sherpas have been an integral part of Everest climbing expeditions from the very beginning. Indeed, very few significant successes have been achieved without them.

Just as Sherpas help climbers reach the top of Everest, technical Sherpas can help entire teams navigate the most specialized, complex, brittle, and least-documented areas of code. Rather than continue to utilize them as pack mules, they can instead transition to the role of advisor and teacher of current-state technology.

Some organizations are great facilitators of this transition through mentoring and career coaching. Not all technical talent is extroverted, and without support they can very naturally slip into the role of pack mule. Don't allow this to happen. Eliminate silos of knowledge by helping your most experienced team members pass their experience along to the next generation.

Summary

Companies who embrace the business value of joy have a remarkable advantage because there are still so many horrible companies creating horrible software. It's simply too easy to compete against miserable companies, especially in the enterprise software market where misery is the norm rather than the exception. More and more companies are recognizing the business opportunity and optimizing for happiness. These companies making a lot of money in the process.

It's an exciting time to be a software developer, and it was an exciting time to attend the first QCon held in New York City. I'll be spending more time thinking about how to do my part and bring happiness to all the teams I work with in the future.

Wednesday
Nov022011

Multiplayer tic-tac-toe in Java using the WebSocket API, Netty (NIO), and jQuery

What does this game of tic-tac-toe and Twitter have in common? Both have been implemented using relatively the same technologies: Java and Netty. It was big news in 2010 when Twitter migrated their search from Ruby on Rails to Java-based Netty. Not only was it big in the news department, but it was also big in the results department: Twitter reported their search performance increased by 3x. 

What is Netty?

Netty is a client server framework by JBoss that simplifies network programming. Netty is built on top of Java NIO but provides a much more simple API to work with. Netty can be used to build a custom server for network communications; it can be used to build anything including a lightweight HTTP server, a TCP / UDP server, a WebSocket server, or any other network server you can dream of. Because Netty is built on Java NIO, Netty's programming model is asynchronous. This means Netty is very well suited for any number of bi-directional communication projects such as real-time group chat or anything else requiring the server to push information to a client rather than other methods of network communication such as long-polling.

What is the WebSocket protocol?

WebSocket is a protocol used for bi-directional asynchronous communications between a client (usually a web browser) and a server that supports the WebSocket protocol.

A WebSocket client connects to a server via standard HTTP and performs a handshake, which creates a persistent tunnel between the client and server. After the handshake is performed the client and the server communicate freely using a message/event-driven programming model (binding actions/methods to events). The beautiful thing about the WebSocket protocol is the number of persistent connections that WebSocket servers can handle, easily numbering in the ten-of-thousands, and the volume of messages that can be processed (depending on the way the server is implemented).

Netty in action - A game of tic-tac-toe

Rather than build the same-old group chat application everyone else does to show off the flexibility of Netty and the WebSocket protocol, I decided to build a simple game of tic-tac-toe instead. It seems like an odd decision considering tic-tac-toe is a turn-based game rather than a real-time game, but most tic-tac-toe game demos on the net are single-player. Rather than build another single player game of tic-tac-toe, let's build a multiplayer game!

The core concepts behind the game are:

  1. The tic-tac-toe client and server can support a (theoretically) infinite number of simultaneous games, and each game supports 2 players.
  2. The player loads the client (a web page) and waits for an opponent.
  3. An opponent loads the client. 
  4. Both players are matched together automatically.
  5. The server responds to both players to let them know their game has started. The server notifies the client which player should go first and each player's assigned letter.
  6. The client only allows one player to select a cell at once.
  7. After each turn, the other player is notified of the other player's selection and their screen is updated automatically.
  8. After each turn the server determines if someone has won or if the game is a draw.

If you'd like to review the full code for the working Netty tic-tac-toe client and server before reading ahead, feel free to check it out:

https://github.com/rocketpages/Netty-TicTacToe-Server

https://github.com/rocketpages/TicTacToe-Client

Building the Netty server

The first step to creating our tic-tac-toe server is to build the server itself. Creating a new server in Netty is dead simple. We simply need to instruct Netty which port to bind to and which pipeline factory to use.

Netty works based on inbound and outbound "handlers"; upstream handlers and downstream handlers. As a message is either received by the server or sent by the server, it is acted upon by the handlers that you specify in the pipeline factory. This is a flexible architecture and lets us work in a very modular fashion on any given message. Anyone who has done MDB, MQ, or SOAP programming (SOAP handler chain) should be familiar with the concept already.

public class TicTacToeServer {
    public static void main(String[] args) throws Exception {
        ChannelFactory factory =
            new NioServerSocketChannelFactory(
                    Executors.newCachedThreadPool(),
                    Executors.newCachedThreadPool());

        ServerBootstrap bootstrap = new ServerBootstrap(factory);

        bootstrap.setPipelineFactory(new WebSocketServerPipelineFactory());

        bootstrap.setOption("child.tcpNoDelay", true);
        bootstrap.setOption("child.keepAlive", true);

        bootstrap.bind(new InetSocketAddress(9000));
        
        System.out.println("TicTacToe Server: Listening on port 9000");
    }
} 

The next step is creating the pipeline factory. In this case we're using a custom pipeline factory called WebSocketServerPipelineFactory. We could also have built this as an anonymous class as our implementation is fairly simple, but I decided to break it out into it's own high-level class.

public class WebSocketServerPipelineFactory implements ChannelPipelineFactory {
	public ChannelPipeline getPipeline() throws Exception {
		// Create a default pipeline implementation.
		ChannelPipeline pipeline = pipeline();
		pipeline.addLast("decoder", new HttpRequestDecoder());
		pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
		pipeline.addLast("encoder", new HttpResponseEncoder());
		pipeline.addLast("handler", new TicTacToeServerHandler());
		return pipeline;
	}
}

Take a second to look at the above code. I'll break out some of the key terms and concepts to understand:

  • Channel: A channel is a persistent connection (tunnel) from a specific client to the server.
  • ChannelPipeline: Each channel can be customized with it's own pipeline. When you take a second to think about it, it becomes obvious how powerful this concept is. Encoders, decoders, aggregator, and handlers are grouped together to form a pipeline. A pipeline instructs Netty how to act on each channel.
  • Encoders, decoders, and aggregators: Netty is a low-level framework built on Java NIO. When a client first connects with the server, we need to perform a WebSocket handshake. NIO doesn't care about HTTP however, it cares about packets. In order to process the HTTP request and response, we need to instruct Netty how to deal with the packets we're receiving and sending. Netty makes this easy and provides multiple encoders, decoders, aggregator, and handlers. We'll typically extend Netty to create our own custom handlers, but if we're so inclined, we can also get really low-level and create our own encoders, etc. Also keep in mind that these can be swapped-out at runtime. After the initial HTTP handshake is performed, we're only going to be responding to WebSocket requests for this channel. You'll see later how we change from an HTTP-based pipeline to a WebSocket-based pipeline for a specific channel (aka client, aka player).
  • TicTacToeServerHandler: This is the heart of the application, responsible for consuming and pushing all messages to and from clients. An instance of this handler is specific to a channel, but we can also declare static variables to keep track of all the games of tic-tac-toe in progress at any given time.

Let's check out the TicTacToeServerHandler. The complete code can be viewed here. I'll discuss some of the core concepts below. 

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
			throws Exception {
		Object msg = e.getMessage();
		if (msg instanceof HttpRequest) {
			handleHttpRequest(ctx, (HttpRequest) msg);
		} else if (msg instanceof WebSocketFrame) {
			handleWebSocketFrame(ctx, (WebSocketFrame) msg);
		}
	}

The messageReceived(...) method is the main callback method provided by Netty to process incoming messages from clients. In our case, we'll only be processing two types of messages: HttpRequest (for the initial handshake from a client) and WebSocketFrame (for all incoming communications after the tunnel has been established). 

private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req)
		throws Exception {

	// Allow only GET methods.
	if (req.getMethod() != HttpMethod.GET) {
		sendHttpResponse(ctx, req, new DefaultHttpResponse(
				HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN));
		return;
	}

	// Serve the WebSocket handshake request.
	if (req.getUri().equals(WEBSOCKET_PATH)
			&& Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION))
			&& WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) {

		// Create the WebSocket handshake response.
		HttpResponse res = new DefaultHttpResponse(
				HTTP_1_1,
				new HttpResponseStatus(101, "Web Socket Protocol Handshake"));
		res.addHeader(Names.UPGRADE, WEBSOCKET);
		res.addHeader(CONNECTION, Values.UPGRADE);

		// Fill in the headers and contents depending on handshake method.
		// New handshake specification has a challenge.
		if (req.containsHeader(SEC_WEBSOCKET_KEY1)
				&& req.containsHeader(SEC_WEBSOCKET_KEY2)) {
			
			// New handshake method with challenge
			res.addHeader(SEC_WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
			res.addHeader(SEC_WEBSOCKET_LOCATION, getWebSocketLocation(req));
			String protocol = req.getHeader(SEC_WEBSOCKET_PROTOCOL);
			if (protocol != null) {
				res.addHeader(SEC_WEBSOCKET_PROTOCOL, protocol);
			}

			// Calculate the answer of the challenge.
			String key1 = req.getHeader(SEC_WEBSOCKET_KEY1);
			String key2 = req.getHeader(SEC_WEBSOCKET_KEY2);
			int a = (int) (Long.parseLong(key1.replaceAll("[^0-9]", "")) / key1
					.replaceAll("[^ ]", "").length());
			int b = (int) (Long.parseLong(key2.replaceAll("[^0-9]", "")) / key2
					.replaceAll("[^ ]", "").length());
			long c = req.getContent().readLong();
			ChannelBuffer input = ChannelBuffers.buffer(16);
			input.writeInt(a);
			input.writeInt(b);
			input.writeLong(c);
			ChannelBuffer output = ChannelBuffers
					.wrappedBuffer(MessageDigest.getInstance("MD5").digest(
							input.array()));
			res.setContent(output);
		} else {
			// Old handshake method with no challenge:
			res.addHeader(WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
			res.addHeader(WEBSOCKET_LOCATION, getWebSocketLocation(req));
			String protocol = req.getHeader(WEBSOCKET_PROTOCOL);
			if (protocol != null) {
				res.addHeader(WEBSOCKET_PROTOCOL, protocol);
			}
		}

		// Upgrade the connection and send the handshake response.
		ChannelPipeline p = ctx.getChannel().getPipeline();
		p.remove("aggregator");
		p.replace("decoder", "wsdecoder", new WebSocketFrameDecoder());

		// Write handshake response to the channel
		ctx.getChannel().write(res);
		
		// Upgrade encoder to WebSocketFrameEncoder
		p.replace("encoder", "wsencoder", new WebSocketFrameEncoder());
		
		// Initialize the game. Assign players to a game and assign them a letter (X or O)
		initGame(ctx);

		return;
	}

	// Send an error page otherwise.
	sendHttpResponse(ctx, req, new DefaultHttpResponse(
			HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN));
}

The code above is performed when the client initially connects to the tic-tac-toe server. The WebSocket specification defines how a handshake needs to be performed. There are two versions of the handshake implemented above: one based on the old WebSocket specification (75), and the other based on the new version of the WebSocket specification (76). The biggest difference between the two handshake methods is the old version does not require a challenge while the new version does. To slightly complicate matters, different browsers implement different versions of the WebSocket specification. Another twist is that neither 75 or 76 is the latest version of the WebSocket specification, but these are the most commonly implemented by modern browsers. A deep dive into the WebSocket specification is beyond the scope of this article, but you can find the latest draft specification here.

This brings up an important point; the WebSocket specification is constantly evolving. Fast. I would strongly recommend exploring the option of using a higher-level framework for business programming rather than rolling your own server, because as you see above, you'll need to keep up-to-date with the latest changes in the spec and which versions are supported by which browsers. It's generally a good idea to leave this up to framework developers. That being said, even if you're planning to use a high-level framework, getting an in-depth knowledge of Netty is a very good thing. Down the road if you need the kind of flexibility and power that Twitter does you'll already know how to implement it.

Also note that we're programmatically changing the aggregator, encoder, and decoder for this channel only. As soon as the handshake is successful we will be communicating with the client exclusively using the WebSocket protocol rather than HTTP. If we were to leave the original HTTP encoder, decoder, and aggregator alone, our server would not understand how to deal with a WebSocket packet; it would still be trying to piece together usable HTTP requests and responses.

Now let's take a look at some tic-tac-toe specific logic. First we'll need to create a game and assign players to it. As players connect to our server they're paired off and assigned to a game. Our server will be able to support an infinite number of games simultaneously, although I doubt tic-tac-toe will ever become as popular as Twitter.

private void initGame(ChannelHandlerContext ctx) {
	// Try to find a game waiting for a player. If one doesn't exist, create a new one.
	Game game = findGame();
	
	// Create a new instance of player and assign their channel for WebSocket communications.
	Player player = new Player(ctx.getChannel());
	
	// Add the player to the game.
	Game.PlayerLetter letter = game.addPlayer(player);
	
	// Add the game to the collection of games.
	games.put(game.getId(), game);
	
	// Send confirmation message to player with game ID and their assigned letter (X or O) 
	ctx.getChannel().write(new DefaultWebSocketFrame(new HandshakeMessageBean(game.getId(), letter.toString()).toJson()));
	
	// If the game has begun we need to inform the players. Send them a "turn" message (either "waiting" or "your_turn")
	if (game.getStatus() == Game.Status.IN_PROGRESS) {			
		game.getPlayer(PlayerLetter.X).getChannel().write(new DefaultWebSocketFrame(new TurnMessageBean(YOUR_TURN).toJson()));
		game.getPlayer(PlayerLetter.O).getChannel().write(new DefaultWebSocketFrame(new TurnMessageBean(WAITING).toJson()));
	}
}

private Game findGame() {		
	// Find an existing game and return it
	for (Game g : games.values()) {
		if (g.getStatus().equals(Game.Status.WAITING)) {
			return g;
		}
	}
	
	// Or return a new game
	return new Game();
}

The code above is mainly responsible for creating and maintaining games, which are stored in the TicTacToeServerHandler as a static collection (shared across all instances of our handler). As players connect to our server we assign them to a game and let them know that they are either waiting for an opponent or that an opponent has connected and their game has begun. Exciting!

Finally, we need to accept incoming WebSocket messages. Each message represents a player's turn (which cell they selected). After we process the turn information we need to push data out to their opponent to let them know how badly they're getting pwned. We also need to check the status of the ongoing game; it's fairly important to be able to tell if a game has been won or tied after the last move!

private void handleWebSocketFrame(ChannelHandlerContext ctx,
		WebSocketFrame frame) {
	
	Gson gson = new Gson();
	IncomingMessageBean message = gson.fromJson(frame.getTextData(), IncomingMessageBean.class);
	
	Game game = games.get(message.getGameId());
	Player opponent = game.getOpponent(message.getPlayer());
	Player player = game.getPlayer(PlayerLetter.valueOf(message.getPlayer()));
	
	// Mark the cell the player selected.
	game.markCell(message.getGridIdAsInt(), player.getLetter());
	
	// Get the status for the current game.
	boolean winner = game.isPlayerWinner(player.getLetter());
	boolean tied = game.isTied();
	
	// Respond to the opponent in order to update their screen.
	String responseToOpponent = new OutgoingMessageBean(player.getLetter().toString(), message.getGridId(), winner, tied).toJson();		
	opponent.getChannel().write(new DefaultWebSocketFrame(responseToOpponent));
	
	// Respond to the player to let them know they won.
	if (winner) {
		player.getChannel().write(new DefaultWebSocketFrame(new GameOverMessageBean(YOU_WIN).toJson()));
	} else if (tied) {
		player.getChannel().write(new DefaultWebSocketFrame(new GameOverMessageBean(TIED).toJson()));
	}
}

Remember earlier that handleWebSocketFrame is one of our own methods, not a callback method provided by Netty. This method is invoked when we inspect the incoming message and determine it's a WebSocketFrame rather than an HttpRequest. The core of the logic above deals with updating the game board and marking the current player's last move. We also need to check for victory or draw conditions and update the player and opponent with the latest game status.

That's it! We've coded an entire tic-tac-toe server from scratch and it was very painless. Netty rocks. The only classes we didn't review are the specific POJOs for tic-tac-toe related logic. You can check out the source below:

Creating the tic-tac-toe client using jQuery

Building our tic-tac-toe server was fairly simple. Building our tic-tac-toe client is even easier thanks to the power of jQuery! If you'd like to skip straight to the source code, it can be viewed below:

https://github.com/rocketpages/TicTacToe-Client

The bulk of the logic is contained in our JavaScript file. After the initial handshake is performed, all communication with the server will be done asynchronously via the WebSocket API. Please note that not all browsers support the WebSocket protocol. This is an up-to-date list of which layout engines support which HTML5 features.

Below is a copy of the JavaScript for the tic-tac-toe client.

// Constants - Status Updates
var STRATEGIZING_STATUS = "Your opponent is strategizing.";
var WAITING_STATUS = "Waiting for an opponent.";
var YOUR_TURN_STATUS = "It's your turn!";
var YOU_WIN_STATUS = "You win!";
var TIED_STATUS = "The game is tied.";
var WEBSOCKET_CLOSED_STATUS = "The WebSocket Connection Has Been Closed.";

// Constants - Game
var PLAYER_O = "O";
var PLAYER_X = "X";

// Constants - Incoming message types
var MESSAGE_HANDSHAKE = "handshake";
var MESSAGE_OPPONENT_UPDATE = "response";
var MESSAGE_TURN_INDICATOR = "turn";
var MESSAGE_GAME_OVER = "game_over";

// Constants - Message turn indicator types
var MESSAGE_TURN_INDICATOR_YOUR_TURN = "YOUR_TURN";
var MESSAGE_TURN_INDICATOR_WAITING = "WAITING";

// Constants - Game over message types
var MESSAGE_GAME_OVER_YOU_WIN = "YOU_WIN";
var MESSAGE_GAME_OVER_TIED = "TIED";

// Constants - WebSocket URL
var WEBSOCKET_URL = "ws://localhost:9000/websocket";


// Variables
var player;
var opponent;
var gameId;
var yourTurn = false;

// WebSocket connection
var ws;

$(document).ready(function() {
	
	/* Bind to the click of all divs (tic tac toe cells) on the page
	   We would want to qualify this if we styled the game fancier! */
	$("div").click(function () {
		// Only process clicks if it's your turn.
		if (yourTurn == true) { 
	      // Stop processing clicks and invoke sendMessage(). 
		  yourTurn = false;
    	  sendMessage(this.id);
    	  // Add the X or O to the game board and update status.
	      $("#" + this.id).addClass(player);
	      $("#" + this.id).html(player);	    	  
	      $('#status').text(STRATEGIZING_STATUS);    	 					      
    	}
    });	

    // On the intial page load we perform the handshake with the server.
    ws = new WebSocket(WEBSOCKET_URL);
    
    ws.onopen = function(event) { 
    	$('#status').text(WAITING_STATUS); 
    }
    
    // Process turn message ("push") from the server.
	ws.onmessage = function(event) {
 		var message = jQuery.parseJSON(event.data);
 		
 		// Process the handshake response when the page is opened
 		if (message.type === MESSAGE_HANDSHAKE) {
   	 		gameId = message.gameId;
   	 		player = message.player;

   	 	 	if (player === PLAYER_X) {
   	 	 		opponent = PLAYER_O; 
   	 	 	} else {
   	 	 		opponent = PLAYER_X;   	 	 	
   	 	 	}
 		}
 		
 		// Process your opponent's turn data.
 		if (message.type === MESSAGE_OPPONENT_UPDATE) {
 			// Show their turn info on the game board.
 			$("#" + message.gridId).addClass(message.opponent);
 			$("#" + message.gridId).html(message.opponent);
 			
 			// Switch to your turn.
 			if (message.winner == true) {
 				$('#status').text(message.opponent + " is the winner!"); 
 			} else if (message.tied == true) {
 				$('#status').text(TIED_STATUS);   	   	 			
 			} else {
 				yourTurn = true;
    			$('#status').text(YOUR_TURN_STATUS);    	   	 			
    		}
 		}   	 	
 		
 		/* The initial turn indicator from the server. Determines who starts
 		   the game first. Both players wait until the server gives the OK
 		   to start a game. */
 		if (message.type === MESSAGE_TURN_INDICATOR) {
 			if (message.turn === MESSAGE_TURN_INDICATOR_YOUR_TURN) {
 				yourTurn = true;
	    		$('#status').text(YOUR_TURN_STATUS);    	 			
    		} else if (message.turn === MESSAGE_TURN_INDICATOR_WAITING) {
				$('#status').text(STRATEGIZING_STATUS);    	 					    	
    		}
 		}
 		
 		/* The server has determined you are the winner and sent you this message. */
 		if (message.type === MESSAGE_GAME_OVER) {
	 		if (message.result === MESSAGE_GAME_OVER_YOU_WIN) {
				$('#status').text(YOU_WIN_STATUS);
			} 
			else if (message.result === MESSAGE_GAME_OVER_TIED) {
				$('#status').text(TIED_STATUS);
			}
 		}	
 	} 
 	
 	ws.onclose = function(event) { 
 		$('#status').text(WEBSOCKET_CLOSED_STATUS); 
 	} 
		
});

// Send your turn information to the server.
function sendMessage(id) {
	var message = {gameId: gameId, player: player, gridId:id};
	var encoded = $.toJSON(message);
	ws.send(encoded);
}

The bulk of the logic above deals with two distinct activities; maintaining state and processing messages. You'll notice that when the page first loads we'll use the documentReady function to initiate the WS handshake. You'll also notice that we use jQuery to bind different WebSocket events to code blocks.

jQuery makes it fairly trivial to communicate with a WS server. Rather than polling the server, the WebSocket API allows us to bind logic to the onmessage event and perform the appropriate logic. This is awesome! Our tic-tac-toe game is a simple webpage that never needs refreshing. We also don't need to waist IO constantly polling the server. As soon as our opponent makes his or her move, our screen is immediately updated and we get to take our turn.

Just the beginning

That's a very high level look at the power of Java, Netty, the WebSocket API, and jQuery. We can make a number of improvements to this game, such as tracking and displaying ongoing stats (win, loss, draw) and even allowing the same player to compete in multiple games of tic-tac-toe at once! Using these technologies we may actually be able to make tic-tac-toe a challenge. Stay tuned!