It has not been an easy week. I have refactored code, dug into bugs that laid in the deep dark corners of framework documentation, was forced to rethink my assumptions, and learned things the hard way. This week was a big move forward for me as a developer, and I’ve gained a lot of perspective about what I am doing with this checklist project.
Hopefully this post shows just a fraction of the things that have been on my mind. I wish I could put them all in a nice neat format, but I am afraid that every time I think I know what I am doing enough to write a blog post about it, I am forced to rethink and relearn the things I was so sure of. This is very good for me, and I will continue to show what that metamorphosis process is like until I can conceptualize and verbalize every element of this project in a logical series of step-by-step instructions on the best way to write a Seneca/Hapi/Angular project.
Right now, I am just doing my best to learn.
A New Soul in Server-land
So far, looking back on the decisions I have made in my code, I have made every possible mistake. I have structured my files incorrectly, forgotten
module.exports, overwritten object properties, even inadvertently obliterating response headers. Searching through bugs and sifting through documentation is a very hard way to learn. It takes hours of incorrectly rubbing sticks together to finally get smoke, which would take a seasoned expert only a matter of seconds.
But this is the best kind of learning. I remember living with a Scottish family for a week over the summer, and watching their son learn to sail in sailing lessons. The father was a seasoned fisherman, and knew about water and the sea more than I will ever know about anything. He watched his son sail with keen interest, to see how he would fare alone on the water.
Just as his son was coming back in, the rudder broke off of his boat. The boat was in danger of tipping over, but he braced himself against the sail and paddled his way into shore. Eventually the boat tipped over, but he still was able to get it on to land. His father said that was the best way for him to learn: to be tested against a challenge and learn what it took to act on his feet.
The Module Boundary
It takes a very long time to understand how to create modules. Using modules is very easy: someone has been smart enough to pre-bake the expected interface for you, and has drawn the lines around where your code ends and where their code begins. I have long been on a plateau of easily using frameworks and third party modules in my code, but never grasping the motivation behind the interface design.
Through trial and error, interface design becomes more and more reasonable. For the longest time I did not even understand the reason for breaking code into modules, but I think I really understand the core of the issue now. It is immensely easier to tweak an internal implementation of a module than to shove your hands into a fragile, shaky monolith and rewire chunks of code with unknown side-effects. As long as clear lines of division are set in place from the beginning, making changes (which are inevitable) can be orders of magnitude easier.
Tests as note-takers
Being able to quickly write good tests is an indispensable skill for a developer. A theorem is only as good as its proof, and a feature is only as good as its test. For my current Angular project, I have been making great use of Protractor, a end to end testing tool for Angular.
Before you mention how awful Jasmine syntax is or how much you hate interacting with WebDrivers, just remember that there is only one way to know if logging in works, and that is manually checking things with a third-party person. Since I do not have a dedicated QA team that can do that repetitive checking for me to make sure my changes have not broken anything, automated user testing is my next best alternative, and it is nice having a tool that is made to work with Angular out of the box. I was heavily considering just going with a simple tape/zuul testing setup, but I wanted to try something more interaction based. It’s not perfect, but I have gotten pretty fast at spinning up tests. If you want to see how I do login testing, check out my test files.
I have also begun using testing as a note-taking tool for the things I haven’t finished yet. I have a problem remembering to put the last nails in the coffins of features, and I’ve started writing test cases that fail unless those features work. That way, when I am smarter in the future and know how to easily solve the things I’m not sure about yet, I can write a test and let the nagging red lights remind me to go back.
I know a lot of people have quoted Mark Zuckerberg’s line “Move fast and break things”, and modified it to “Move fast and break nothing” or other spin offs. My idea of a good motto is “Move fast and know what you break”. Things will get broken, and having a few cursory regression tests can go a long way in preventing things from getting broken beyond repair.
If you don’t like Protractor, I’ve been really wanting to try out intern. And if you are feeling really crazy (and don’t mind integrating a bit of ruby into your project) I have used and highly recommend Capybara, which feels mostly like magic when you see how simple the syntax is.
Gotchas and Freebies
So, now that I’ve written out the philosophical points that I’ve learned this week, here are some things that you might really be interested in:
My code for converting Hapi reply and request objects into Express response and request objects has hit npm. It currently has a very limited API, and my goal is that before February, I will take all of Express’s test suite and make sure that my facades can stand the tests. I will then be creating a Hapi plugin called hapi-middleware that will just take middleware functions as options and call each of them on requests. If anyone wants to help me out, I need to start copying over tests, so please let me know! Seems like people could really benefit from this.
When working with Angular and authentication, I was having some major difficulty using cookies from the server to do authentication. I was implementing (and still haven’t quite refactored) an implementation that kept track of login information within Angular, but would log out the user if they reloaded. The heart of the issue was in the cross-origin requests I was making to the authentication API, which I was running on a different port for the sake of decoupling. After searching for a long time, I finally found information about using the withCrentials parameter for XMLHttpRequests. The one line that made all the difference was the following:
… the browser will reject any response that does not have the Access-Control-Allow-Credentials: true header, and not make the response available to the invoking web content.
The documentation in Hapi was buried pretty deep, but I found a
cors.credentialsmethod in the route configuration that made everything work like butter. I will need to refactor that later, so I made a quick failing test for it and moved on with building the rest of the app.
Speaking of modularizing first and fixing later, I’ve been heavily relying on Angular UI router from the beginning of this project, just to make sure my UI can be nested and repeatable. The Ionic Framework relies on this library heavily, and it makes the mobile side of things look very near to native. Eventually the checklist app may be heavily mobile-focused, and making the call early to handle routing with nested states will make a big design difference later on.
That ends this week. As I begin to use seneca-project and seneca-card I begin to see the value in the abstraction seneca provides. Abstractions are incredibly nice, but require a bit of cognitive overhead from the beginning. It was difficult to finally understand why all of this work to set up the project with Hapi was worth it, but I think I’m beginning to see why. It can be hard to explain to someone in algebra class why calculus is important, but once you really learn calculus and can prove theorems in leaps and bounds, you realize that you have been doing it the hard way for years. Calculus abstracts away the inner workings of functions and allows you to interact with functions in general, just like Seneca abstracts the details of implementation and just worry about business logic.
But without a solid knowledge of algebra, there is no way you can find calculus intuitive or useful. I am still in the process of learning about data entities and authentication methods, while Seneca abstracts these into general actions. Seneca is not made for pre-algebra students. I am very close to understanding Seneca fully, but it was a very hard fought road to get here. Maybe I can help ease this learning curve in later blog posts that walk through a Seneca project and train even beginner learners to use the power of Seneca to solve non-trivial problems quickly.