Simple software engineering: Inject dependencies when possible

Why should dependencies be injected?

Code should not instantiate or otherwise access its own dependencies. Instead, prefer to pass in dependencies as arguments.

This should be done when code becomes important enough to unit test. Dependency injection makes it easier for tests to provide dependencies with different configurations. It also makes it easier to inspect the side effects introduced onto dependencies. It will also make maintenance easier because it will increase flexibility at little cost.

When working with I/O, pass in interfaces instead of instantiating classes

// Avoid new()ing the dependency
public function getDatabaseConnectionInfo(): ConnectionInfo {
    $redis = new Redis();
    return new ConnectionInfo(
        $redis->get('host'),
        $redis->get('port')
    );
}

// Prefer to pass the dependency in
public function getDatabaseConnectionInfo(
    Redis $redis
): ConnectionInfo {
    return new ConnectionInfo(
        $redis->get('host'),
        $redis->get('port')
    );
}

// Passing in an interface is even better
public function getDatabaseConnectionInfo(
    KeyValueInterface $key_value_store
): ConnectionInfo {
    return new ConnectionInfo(
        $key_value_store->get('host'),
        $key_value_store->get('port')
    );
}

Dependency injection makes testing easier.

The unit test can take a trivial in-memory store instead of either wrangling a Redis instance or mocking a constructor.

Additionally, code that manages its own dependencies can become nested deep within other code. If the first example above became deeply nested, it would be unclear that the corresponding test must manage a key/value store. This could lead to bad surprises like tests attempting to connect to Redis.

Dependency injection increases flexibility at little cost.

In the example above, it may become necessary to introduce a caching layer in front of the key/value store. This is easy with dependency injection. Just make a new class that inherits the interface and delegates to the I/O layer on cache misses. Without dependency injection, it becomes a project to find and fix all usages.

Dependency injection makes it easier to make application-wide changes.

In the above example, it may become necessary to stop using the default Redis database and configure which Redis database should be used. If the Redis class is instantiated in many places, this becomes a sizable effort. Compare this to changing the single invocation that is injected throughout the application. The latter will usually be much easier.

Pass the result of I/O into business and presentation logic

// Avoid performing I/O in business logic
public function isShopTemporarilyClosed(
    ORM $orm, int $shop_id
): bool {
    // All shops manually turned off by the owner
    // are called temporarily closed.
    $shop = $orm->getFinder('Shop')->findById($shop_id);
    return $shop->is_off
        && $shop->owner->id === $shop->disabled_by_user->id;
}

// Prefer passing the result of I/O into business logic
public function isShopTemporarilyClosed(
    Shop $shop
): bool {
    // All shops manually turned off by the owner
    // are called temporarily closed.
    return $shop->is_off 
        && $shop->owner->id === $shop->disabled_by_user->id;
}

Business/presentation logic should not be overly opinionated

Applications often have several choices about where they can read equivalent data. This function shouldn’t care that the data came from the ORM. Why couldn’t it be passed in the POST data of a request? Or be fetched from a REST API? Ideally, logic that acts on a shop model should work anywhere.

Doing I/O in application logic makes its callers difficult to refactor

As a codebase grows, helper functions may acquire dozens or hundreds or thousands of callsites. They may become nested deep within the application call stack. It will be used within business-critical logic that will run into scaling problems. Manually managing dependencies makes it difficult to perform some optimizations. For example, it’s difficult to ensure that the program never makes redundant I/O calls when the object is accessed via I/O dozens of times in a codebase. This is true even with caching! It’s often the case that calling two different I/O entry points (or the same entry point with different arguments) can produce the same results. This can be difficult or impossible to programmatically detect, even though it may be obvious to the application developer.

Passing in the result of I/O makes it easier to share the result of I/O among different callers.

I/O introduces nondeterministic behavior
It would be surprising to see a DatabaseReadException when calculating whether a shop is closed. But introducing I/O into a call increases the risk that code can throw exceptions for nondeterministic reasons like service availability.

I/O also dramatically affects timing metrics. Let’s say that I/O calls are cached, and the shop is fetched in two places: once while deciding which views to render, and once while rendering the view. Later, a programmer realizes that they don’t need to perform the first fetch. They remove it. This will move the I/O call from the application logic into the view logic, causing the view logic’s timing instrumentation to increase. This is because a former cache hit is now a cache miss with I/O fetching. No regression happened, but the application performance graphs make it seem like one did.

This could also cause tests to become flaky if they actually perform the I/O and sometimes fail.

Instead, prefer to centralize or share logic related to I/O. The details of this will depend on which languages and libraries are used.

Don’t access static or global state within business or presentation logic

// Avoid accessing global or static state in business logic
public function isLocaleEnUs(): bool {
    return strtolower($_REQUEST('locale')) === 'en-us';
}

// Pass global or static state into business logic
public function isLocaleEnUs(string $locale): bool {
    return strtolower($locale) === 'en-us';
}

Accessing static and global state is unsafe across machines

A helper that access static or global state makes strong assumptions about what happened on the machine prior to the code executing. Note: this refers to static state – the reliance on information from the execution environment, or calculated data that is stored statically. Accessing static data or static functions isn’t included in this.

If code lives long enough, it will eventually execute in several layers of the same application stack. Think about all the different application architectures that can exist in the same company at the same time. Reverse proxies in front of long-lived application servers, CGI scripts, batch processing jobs, monoliths, microservices, single-page applications, mobile apps, serverless lambdas, server-side rendering, etc. And to add another dimension, there are quite a few transport mechanisms available: HTTP, RPC, IPC, etc.

As code becomes longer-lived, it will eventually live within several layers at the same time. This introduces unnecessary complexity on each of the additional layers. If some logic directly reads the request parameters to determine the locale, that it (and every thing that ever depends on it) will always need to execute within an HTTP request. Or it must fake the HTTP request environment when it is included in a layer without HTTP. Or if it’s proxied within HTTP, the proxied call will also need to forward the request parameters, even if it doesn’t make semantic sense.

How to move an existing codebase towards dependency injection

This can be done incrementally. For each commit that uses a dependency, refactor that dependency to come from one layer higher in the stack. This is a good opportunity to introduce tests for untested code, or simplify tests for existing code.

Over time, frequently-modified code will become fully implemented using dependency injection. It may be necessary to do a special project to modify, replace, or delete code that hasn’t been touched in years. But maybe it’s fine to just leave it. After all, it hasn’t been modified in years.

Rupert in booties

Failing really fast – Week 3 of learning about business

Last week

I wanted to test the idea that I could sell trivia night question/answer sheets for one-off events. I made a set of 18 Super Bowl questions and answers along with a set of rules. I was just waiting for Stripe to approve my application so that I could accept payments.

This week

I had a cold starting on Wednesday, and didn’t start feeling better until the weekend. That means that I didn’t start making progress until Saturday.

Stripe was still reviewing my application when the weekend started. I looked into other payment methods that can be integrated with Squarespace. They also accept PayPal business accounts. I happen to have one of these!

So I made a small sales site basically designed to try to just sell the Super Bowl trivia sheet that I set up. I then set up and started tweaking three Google Ads to get a cheap CPC while still showing up on qualifying searches. I haven’t had any sales since then. Here are the numbers

Impressions1806
Clicks81
Clickthrough Rate4.49%
Visits144
Conversions0
Conversion rate0%
Cost per click0.50

OK! So, some positives and some negatives here. Let’s start with the negatives. The Google Ad console says it best:

Low quality score component. Landing page experience: below average
Landing page experience: below average

Harsh but fair, Google! I have a few ideas for what the contributing factors are for not getting any sales. I think that all of them are at least partially right.

First, asking for somebody to pay money on an unfamiliar site that hasn’t demonstrated any value is asking a lot. In the past few months I’ve done a lot of reading on how SaaS businesses operate. Their primary goal is to get an email address that they can use to send marketing material. For instance, “Sign up for this 6 email course on getting a high-quality email list” or “give us your email, and in exchange we will send you this guide to having a killer landing page.” And the idea is that by having the email address, you have a way to keep reminding them that you exist. Plus, they are more likely to convert at the end of the 6 email course (since you have hopefully given them information that they find valuable). It’s not clear to me if this extends more to a consumer-minded market.

Second, the price is likely a factor. I was charging $14.99 for the trivia night. My theory was that it replaces 2+ hours of hunting down questions, and another hour of setting the rules. If the person valued their time at all, it would be worth it for them to buy the pre-bundled one. I just dropped the price down to 9.99 to see if price sensitivity mattered at all. I’ll report back next week! My instinct is that I’ll finish out the week without any sales, but I think it’s worth another $20 to find out if my conversion rate is “near zero” or “absolutely zero.”

Third, this is the first landing page that I’ve ever tried to build. As the Google Ad said, “the landing page experience is below average.” I will focus on reading resources for building good landing pages. I will also find landing pages of other small products to see how they develop the real estate they have.

It wasn’t all bad! The ads I ran successfully generated clicks. In my opinion, they were also clear about what I was offering. This means that I was better at identifying a need than I was at closing a deal. This tells me that this idea isn’t pure trash. Pursuing the trivia idea means that I must figure out another landing page strategy to work well with an advertisement like this.

Host Super Bowl Bar Trivia - Prepackaged Super Bowl Trivia
The most successful ad I ran at getting clicks.

Cash flow this week

It has cost me $82 to run this experiment so far. $40 for ads, $12 for the domain, and $30 for 1 month of a commerce Squarespace site.

What am I doing this week?

I have the Squarespace site for another 25 days. This means that I can save myself a second Squarespace setup fee by running a second commerce site experiment just by switching the domain. I also got my Stripe account approved, which is great! I’d rather accept Stripe than PayPal.

The most obvious second experiment that I can run is for Valentine’s Day trivia. I need to do some traffic sizing to validate that this is a good subject. For the sake of this blog post let’s assume that it is. My hunch is that this will be used in a school setting, as opposed to sports trivia which is destined for a bar. Keyword analysis would verify this by looking for phrases like “valentine’s day trivia easy.”

I will also spend time this week researching landing pages. copyhackers.com is probably a good place to start. I can also skim startupsfortherestofus.com and kalzumeus.com to see if they have any other advice. If the Valentine’s Day trivia starts to pan out, I can also try to see if there are other sites that sell these kinds of materials (trivia, bingo cards, etc) to see how they set up their offering. It could be that selling printable materials is the most important thing here.

I may also see if teacherspayteachers.com allows non-teachers to sell on their marketplace. It wouldn’t hurt to try to sell it through a second channel.

Getting Ready to Launch – Week 2 of learning about business

Previous post here

When we left off last week, I had done some keyword analysis on ideas for running a microbusiness. The most promising one was selling trivia questions to people who wanted to run their own trivia nights. I identified the holidays in February as a possible validation vector. But those are too far in the future. I wanted to validate the idea much sooner.

My dog is lying on my lap
Rupert is unimpressed by my efforts, but I love him anyways

I had a realization just after I published last week’s blog post. The Super Bowl is coming up! So I redid my analysis. Keyword traffic around the Super Bowl spikes in the 2 weeks prior. The volume is high enough to be interesting. This is a great candidate for an initial test.

This means that I have a hypothesis. “I can profitably produce and sell trivia questions for major sporting events in the weeks leading up to the events.” But how I can cheaply validate my hypothesis? Some types of trivia lend themselves to existing marketplaces. “President’s Day Trivia” could be tested on a marketplace like Teachers Pay Teachers. But there doesn’t seem to be a marketplace for 2 hour bar trivia. And I’d be surprised if there were. So building a Squarespace site is my next best option with payments powered by Stripe. I could get this off the ground in days instead of weeks.

So I started signing up for a Stripe account. It hasn’t been approved yet. I can’t charge for anything until it is approved. Hopefully this will unlock soon and I will be on my way!

I also wrote out the Super Bowl trivia night. It took about 4 hours over 2 days. About half of that time was spent writing the rules and structuring the game. I have another 30 minutes ahead of me to create an Excel score sheet and validate that it imports into Google Sheets correctly, and I want to go through Stripe’s landing page copywriting guide and apply it to making the Squarespace landing page.

I’ve also started thinking about reducing the time it takes to produce a trivia night. I would want to make a way to generate a trivia night based on the questions and format. This sounds easy, and it would allow me to pay others to write the questions for me without distracting them with the trivia format. From there it would be possible to auto-generate some questions with an existing knowledge bank. This would allow me to gradually reduce how much I would farm out over time.

But that’s getting really far ahead of myself! I wouldn’t do any automation for weeks or months. A better question to ask myself is “What can I do to publish the Squarespace site as soon as possible when the Stripe account is approved?” And that means that I should be reading about landing page copy, making a score sheet that trivia jockeys can use, and finally building out a draft version of the site that I can publish the instant it can go live. Then I will document my process while the test is running. That hopefully means that my results would be repeatable if they are good.

How am I progressing towards my goal?

Make $100 of profit, not counting the value of my time, on a business idea by the end of March, 2019

My first business goal

I think I’m on track so far. I didn’t sign up for Stripe early because it was unclear if an existing market could validate my idea. But now I’m blocked on the sign up process! This feels like a minor unforced error. I’m not beating myself up over it. But there’s a lesson here: there would be no consequence to signing up and not using a Stripe account, so I should have done it early to have it in case I needed it. Next time!

There are positives too. I’ve identified an area where there is interest. I’m thinking of charging $14.99 for the test trivia, since it’s worth far more for a bunch of people to show up at a bar. So after domain + Squarespace + ads I’d likely hit this goal with 10 units sold. This could be a tall order. If both my clickthrough and conversion rates are bad, 4000 searches might only mean 2 sales. And that assumes that I’m selling something that people want! And it’s priced correctly! But it’s something to learn from. The real value is likely to either come long-term with a long-tail search strategy, or a monthly subscription model to people who run trivia nights.

Tune in next week, when I’ve hopefully started collecting clickthrough rate and conversion numbers for the site that I published!