tag:blogger.com,1999:blog-23297975595003559702024-03-05T09:54:59.262-05:00code serving text serving codeEssays on distributed and sociotechnical systems, programming languages, software architecture, and domain driven design.Alec Henningerhttp://www.blogger.com/profile/00830222565415954531noreply@blogger.comBlogger6125tag:blogger.com,1999:blog-2329797559500355970.post-77993202220510281412020-11-07T15:11:00.003-05:002020-11-10T19:46:57.865-05:00The secret world of testing without mocking: domain-driven design, fakes, and other patterns to simplify testing in a microservice architecture<meta name="id" content="7799320222051028141">
<meta name="labels" content="testing,object oriented programming,java,microservices,domain-driven design">
<meta name="title" content="The secret world of testing without mocking: domain-driven design, fakes, and other patterns to simplify testing in a microservice architecture">
<meta name="description" content="How to simplify microservice testing through a few simple applications of domain-driven design and in-memory test doubles (fakes).">
<p>Much has been said about mocks, the controversial, Swiss army knife of test doubles: </p>
<ul>
<li>Don't use them too much <a href="https://github.com/mockito/mockito/wiki/How-to-write-good-tests#dont-mock-everything-its-an-anti-pattern" target="_blank" rel="noreferrer noopener">(source)</a> <a href="https://testing.googleblog.com/2013/05/testing-on-toilet-dont-overuse-mocks.html" target="_blank" rel="noreferrer noopener">(source)</a></li>
<li>Know when to use them at all <a href="https://testing.googleblog.com/2013/03/testing-on-toilet-testing-state-vs.html" target="_blank" rel="noreferrer noopener">(source)</a></li>
<li>Don't use them to test implementation detail <a href="https://testing.googleblog.com/2015/01/testing-on-toilet-change-detector-tests.html" target="_blank" rel="noreferrer noopener">(source)</a> </li>
<li>Don't mock types you don't own <a href="https://github.com/mockito/mockito/wiki/How-to-write-good-tests#dont-mock-a-type-you-dont-own" target="_blank" rel="noreferrer noopener">(source)</a></li>
<li>Only mock classes when dealing with legacy code <a href="https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#spy" target="_blank" rel="noreferrer noopener">(source)</a></li>
<li>Don't mock complex interfaces <a href="https://testing.googleblog.com/2018/11/testing-on-toilet-exercise-service-call.html" target="_blank" rel="noreferrer noopener">(source)</a> <a href="https://www.endoflineblog.com/testing-with-doubles-or-why-mocks-are-stupid-part-4#2-mocks-are-stupid-and-so-are-stubs-" target="_blank" rel="noreferrer noopener">(source)</a></li>
</ul>
<p>...the list goes on. For a tool so easy to misuse, we're using it quite a lot. Mockito is <a href="https://github.com/mockito/mockito/wiki/Mockito-Popularity-and-User-Base" target="_blank" rel="noreferrer noopener">one of
the most depended-upon Java libraries in the world</a>.</p>
<aside>While "mocking" is an abstract concept, for the remainder of this post I'll use the term <var>mock</var>
to refer specifically to a mock or stub configured by way of a mocking library like Mockito.
Likewise, when I refer to Mockito, I really mean any mocking library; Mockito just stands out
because it has a welcoming API and is–no doubt as a consequence–measurably very popular.</aside>
<p>I was there too once, a frequent Mockito user, perhaps like you are now. Over time however, as my
application architectures improved, as I began to introduce <a href="http://qala.io/blog/anaemic-architecture-enemy-of-testing.html" target="_blank" rel="noreferrer noopener">real domain
models</a>, the tests I wrote were becoming simpler, easier to add, and
services easier to develop. Tricky testing problems that loomed over my head for years now had
obvious solutions. Much to my surprise, I was barely using Mockito at all.</p>
<p><strong>In this post, I demonstrate some compelling and, in my experience, overlooked advantages to mock
alternatives.</strong> We will explore the origins of mocking, why mocking may have become so ubiquitous, a
world without mocking, and the system of incentives, practices, and abstractions that evolve as a
result. Whether you are a casual or devout mock-ist, I encourage you to keep calm, open your mind,
and try going without for a while. This post will guide you. You may be surprised what you find.</p>
<h2 id="the-hidden-burdens-of-mocking">The hidden burdens of mocking</h2>
<p>We forget because the APIs are so nice, but <strong>mocking is fascinatingly complex under the hood.</strong>
It's <var>metaprogramming</var>: code that implements types at runtime rather than using native language
features to implement types at compile time.</p>
<p>Mockito's API optimizes for immediate convenience–justifiably so–but <strong>it's this immediate
convenience that dominates our thinking.</strong> While less sexy, a compile-time implementation (a
class) has its own conveniences. Unfortunately, they are easily overlooked because they take just a
little time and investment in the short term before you can see them. By first reviewing some
mocking pitfalls, we'll start to see how taking the time to write a class can pay off.</p>
<aside>Most times, we don't see the complexity required to implement runtime metaprogramming as a
tradeoff, thanks to Mockito's well-designed abstractions. But occasionally, those abstractions
leak. Once, a colleague and I spent a while banging our heads against build failures for just one
particular service when we moved our Jenkins server inside a container in an OpenShift
environment. This was the error:
<pre><code>Caused by: java.io.IOException: well-known file /tmp/.java_pid735 is not secure: file's group should be the current group (which is 0) but the group is 1000330000
</code></pre>
<p>Obvious, right? 😅 It turned out, if you want to accomplish some particularly scandalous tasks
like mocking final classes, Mockito attaches a Java agent <em>at runtime.</em> Because the current
user's group ID didn't match the Java process's group ID (an otherwise uninteresting detail of
the environment, at least to us mere developers), the process was not allowed to attach an agent
to itself.</p>
</aside>
<p>When a class under test has a mocked dependency, the dependency must be stubbed according to the
needs of your test. We only stub the methods our class needs for the test, and only for the
arguments we expect the class to use.</p>
<p>However, by leaving out stubbing some methods, we imply we know <em>what</em> methods are used. By only
stubbing for certain arguments, we imply we know <em>how</em> those methods are used. If our implementation
changes, <a href="https://testing.googleblog.com/2013/05/testing-on-toilet-dont-overuse-mocks.html" target="_blank" rel="noreferrer noopener">we may need to update our tests</a>, even though the behavior hasn't
changed.</p>
<pre><code class="language-java">// A hypothetical anti-corruption layer encapsulating credit.
// Please forgive the very naive domain model.
interface CreditService {
CreditStatus checkCredit(AccountId account);
void charge(AccountId account, Money money);
}
// A hypothetical domain service which depends on a CreditService
class OrderProcessor {
final CreditService creditService;
// snip...
void processOrder(AccountId account, Order order) {
if (HOLD.equals(creditService.checkCredit(account))) {
throw new CreditHoldException(account, order);
}
// snip...
}
}
class OrderProcessorTest {
// snip...
@Test
void throwsIfAccountOnCreditHold() {
when(creditService.checkCredit(AccountId.of(1))).thenReturn(HOLD);
assertThrows(
CreditHoldException.class,
() -> orderService.processOrder(account1, testOrder));
// The above test works with the current implementation, but what if our
// implementation instead changes to just call `charge` instead of first
// calling `checkCredit`, relying on the fact that `charge` will throw an
// exception in this case? The test will start failing, but actually there
// is no problem in the production code. This test is coupled to
// implementation detail.
}
}
</code></pre>
<aside>Please note the domain model in this blog post is ... not great. A good model incorporates
substantial business expertise, and I am no order processing expert. Nor should anyone really be
<a href="https://twitter.com/patio11/status/1321851664551145472?s=20" target="_blank" rel="noreferrer noopener">writing their own order processing model in this day and
age</a>.</aside>
<p>The reverse can also happen: your test passes, but the code actually doesn't work. For example, if
an interface encapsulates some state between subsequent method calls, or a method has some
preconditions or postconditions, and <a href="https://www.endoflineblog.com/testing-with-doubles-or-why-mocks-are-stupid-part-4#2-mocks-are-stupid-and-so-are-stubs-" target="_blank" rel="noreferrer noopener">your stub does not reimplement these
correctly</a>, your tests may not be valid. That is, mocking also repeats, and is
therefore coupled to, how a dependency <em>works</em>.</p>
<p>To remove some of this repetition, we can refactor the test setup to be done once in a <code>@BeforeEach</code>
method for the whole class. You can even go one step further and pull out the stubbing into a static
method, which can be reused in multiple test classes.</p>
<pre><code class="language-java">class Mocks {
// A factory method for a mock that we can reuse in many test classes.
// Note however the state of the stub is obscured from our tests, hurting
// readability.
static CreditService creditService() {
var creditService = mock(CreditService.class);
when(creditService.checkCredit(AccountId.of(1))).thenReturn(HOLD);
doThrow(NotEnoughCreditException.class)
.when(creditService).charge(AccountId.of(1), any(Money.class));
return creditService;
}
}
</code></pre>
<p>There is another way to make an implementation of a type reusable so that you don't have to
constantly reimplement it: the familiar, tool-assisted, keyword-supported, fit-for-purpose <em>class</em>.
Classes are built-in to the language to solve precisely this problem of capturing and codifying
knowledge for reuse in a stateful type. Write it once, and it sticks around to help you with the
next test. <strong>Not only do classes elegantly save you from reimplementing a contract for many tests,
they make implementing those contracts simpler in the first place.</strong></p>
<pre><code class="language-java">// A basic starting point for a "fake" CreditService.
// It sets the foundation for many improvements, outlined below.
// You could even use a mock under the hood here, if you wanted, and change it
// later. Part of the benefit of a class is that you can change the implementation
// over time without breaking your tests.
class InMemoryCreditService implements CreditService {
private Map<AccountId, CreditStatus> accounts =
ImmutableMap.of(AccountId.of(1), CreditStatus.HOLD);
@Override
public CreditStatus checkCredit(AccountId account) {
return accounts.getOrDefault(account, CreditStatus.OK);
}
@Override
public void charge(AccountId account, Money money) {
if (CreditStatus.HOLD.equals(checkCredit(account))) {
throw new NotEnoughCreditException();
}
}
}
</code></pre>
<h2 id="object-oriented-test-double">Object-oriented test double</h2>
<p>Admittedly, our class isn't all that impressive yet. We're just getting warmed up. <strong>A class's real
power comes from encapsulation.</strong> A class is not just a collection of delicately specific stubs, but
a persistent, evolvable and cohesive implementation devoted to the problem of testing.</p>
<p>When all you need is a few stubbed methods, mocking libraries are great! <strong>But the convenience of
these libraries has made us forget that we can often do much better than a few stubbed methods.</strong>
Just as when we aimlessly add getters and setters, habitual mocking risks missing the point of
object-orientation: objects as reusable, cohesive abstractions.</p>
<p>For example, test setup often has a higher order semantic meaning mock DSLs end up obfuscating. When
we stub an external service as in the example above...</p>
<pre><code class="language-java">when(creditService.checkCredit(AccountId.of(1))).thenReturn(HOLD);
doThrow(NotEnoughCreditException.class)
.when(creditService).charge(AccountId.of(1), any(Money.class));
</code></pre>
<p>...what we are really saying is, "Account 1 is on credit hold." Rather than reading and writing a
mock DSL that speaks in terms of methods and arguments and returning and throwing things, we can
<em>name</em> this whole concept as a method itself.</p>
<pre><code class="language-java">// Evolving our class to do more for us
class InMemoryCreditService implements CreditService {
private Map<AccountId, CreditStatus> accounts = new LinkedHashMap<>();
public void assumeHoldOn(AccountId account) {
accounts.put(account, CreditStatus.HOLD);
}
@Override
public CreditStatus checkCredit(AccountId account) {
return accounts.getOrDefault(account, CreditStatus.OK);
}
// charge implementation stays the same...
</code></pre>
<p>Using it, our test reads like our business speaks:</p>
<pre><code class="language-java">creditService.assumeHoldOn(AccountId.of(1))
</code></pre>
<p><strong>Now this concept is reified for all developers to reuse (including your future self).</strong> This is
encapsulation: naming some procedure or concept that we may refer to it later. It builds the
<a href="https://martinfowler.com/bliki/UbiquitousLanguage.html" target="_blank" rel="noreferrer noopener">ubiquitous language</a> for your team and your tools. Having an obvious and
discoverable place to capture and reuse a procedure or concept that comes up while testing:
<em>that's</em> convenience.</p>
<p>I find myself using methods like these constantly while testing, further immersing my mind in the
problem domain, and it is incredibly productive.</p>
<h2 id="fakes-over-stubs">Fakes over stubs</h2>
<p>As your class becomes more complete, it'll start to look more like a <a href="https://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs" target="_blank" rel="noreferrer noopener"><var>fake</var></a> than a
<var>stub</var>. You've used a fake any time you've tested with an in-memory database. A fake is a complete
implementation of some interface suitable for testing.</p>
<p>Any time you replace a non-trivial dependency, you should really ensure the replacement has its own
tests. <strong>This ensures that when you use a test double instead of the real thing, you haven't
invalidated your tests.</strong> If you're clever, you can even reuse the same tests as your production
implementation–and you absolutely should. It saves you time and gives you confidence. </p>
<p>In this way, a fake also becomes a demonstration of how some type is supposed to work. It's can
become a kind of reference implementation and testbed, serving as documentation for ourselves, our
teammates, and our successors.</p>
<pre><code class="language-java">// Example pattern to test a fake and production implementation against same tests
/** Defines the contract of a working repository via tests. */
abstract class RepositoryContract {
SomeAggregateFactory factory = new SomeAggregateFactory();
abstract Repository repository();
@Test
void savedAggregatesAreRetrievableById() {
var aggregate = factory.newAggregate(repository().nextId());
repository().save(aggregate);
assertEquals(aggregate, repository().byId(aggregate.id()));
}
// etc...
}
class InMemoryRepositoryTest extends RepositoryContract {
InMemoryRepository repository = new InMemoryRepository();
@Override
Repository repository() { return repository; }
}
class MongoRepositoryTest extends RepositoryContract {
@RegisterExtension
MongoDb mongoDb = new MongoDb();
MongoRepository repository = new MongoRepository(mongoDb.database("test"));
@Override
Repository repository() { return repository; }
}
</code></pre>
<aside>Fakes can avoid cross-cutting, production, and operational concerns that cause a lot of
complexity in test setup, and aren't the focus of most of your tests anyway. For example, they can
just ignore solutions for nonvolatile persistence and high-performance concurrency control that we
expect of our production persistence abstractions, and which usually require a full database (such
as in the example above, which would require downloading, starting, and managing a MongoDB process).
An implementation can avoid the filesystem all together with in-memory state, and can <code>synchronize</code>
all of its methods to quickly make it thread-safe.</aside>
<h2 id="fakes-as-a-feature">Fakes as a feature</h2>
<p>As the software industry is increasingly concerned with safe, frequent production rollouts, fakes
increasingly make sense as a shipped <em>feature of our software</em> rather than merely compiled-away test
code. As a feature, fakes work as in-memory, out-of-the-box replacements of complicated external
process dependencies–dependencies which may not even yet be specified–and the burdensome
configuration and coupling they bring along with them. <strong>Running a service can then be effortless by
way of a default, in-memory configuration</strong>, also called a <a href="https://testing.googleblog.com/2012/10/hermetic-servers.html" target="_blank" rel="noreferrer noopener">hermetic server</a> (as in
"hermetically sealed"). As a feature, it is one of developer experience, though it still <a href="https://itrevolution.com/book/accelerate/" target="_blank" rel="noreferrer noopener">profoundly
impacts customer experience through safer and faster delivery</a>.</p>
<p>The ability to quickly and easily start any version of your service with zero external dependencies
is game changing. A new teammate can start up your services locally with simple system setup and one
command on their first day. Other teams can realistically use your service in their own testing,
without understanding its ever-evolving internals, and without having to rely on expensive
<a href="https://www.thoughtworks.com/radar/techniques/enterprise-wide-integration-test-environments" target="_blank" rel="noreferrer noopener">enterprise-wide integration testing environments</a>, which inevitably <a href="https://www.honeycomb.io/blog/testing-in-production/" target="_blank" rel="noreferrer noopener">fail to
reproduce production anyway</a>. Additionally, your service's own automated tests
can interact with the entire application (testing tricky things like JSON serialization or HTTP
error handling) and retain unit-test-like speed. And you can run them on an airplane.</p>
<aside>Fakes can even help test operational concerns. A colleague of mine recently needed to load test
her service under certain, hard-to-reproduce conditions involving an external integration (a SaaS,
no less). Rather than interrupting and waiting on the team which manages that SaaS, she simply
reconfigured the service to use an in-memory fake. With such a fake you can even set up specific
conditions of operation, like slow response times (a bit like a chaos experiment). Meanwhile, other
dependencies, which needed to be load tested, kept their production, external configuration. She was
able to hammer some dependencies, which were under her control and supervision, while the rest were
blissfully undisturbed. Her next release went off without a hitch.</aside>
<h2 id="this-is-your-test-this-is-your-test-on-drugs">This is your test. This is your test on drugs.</h2>
<p>"Unit" tests (sometimes called "component" tests) in the ontology of testing, isolate a unit of code
to ensure it functions correctly. We often contrast these with "integration" tests (confusingly,
sometimes also called component tests), which test units together, without isolation. We heard
writing lots of unit tests is good, because of something about a <a href="https://docs.google.com/presentation/d/15gNk21rjer3xo-b1ZqyQVGebOp_aPvHU3YH7YnOMxtE/edit#slide=id.g437663ce1_53_98" target="_blank" rel="noreferrer noopener">pyramid and an ice cream
cone</a>, so we have to make sure most of our tests only use isolated
units, so that most of our tests are unit tests.</p>
<p>So let's back up. <strong>Why are we replacing dependencies and "isolating units" in the first place?</strong></p>
<ul>
<li>With stubbed dependencies, there are fewer places to look when there is a test failure. This means
we can fix bugs faster, so we can <em>ship to our users more frequently</em>.</li>
<li>Dependencies can be heavy, like databases or other servers which take time to set up, slowing down
the tests and their essential feedback. Replacing those with fast test doubles means faster feedback
cycles, and faster feedback cycles means we can <em>ship to our users more frequently</em>.</li>
</ul>
<p>These two <a href="https://mikebroberts.com/2003/07/29/popping-the-why-stack/" target="_blank" rel="noreferrer noopener">why stacks</a> all eventually converge at the same reason, <strong>the reason we
write tests in the first place: to ship more value, more quickly</strong> (after all, <a href="https://www.heavybit.com/library/podcasts/o11ycast/ep-23-beyond-ops-with-erwin-van-der-koogh-of-linc/" target="_blank" rel="noreferrer noopener">features which
improve safety also improve speed</a>). While replacing collaborators can help as
described, replacing collaborators <em>also</em> has effects directly counter to this end goal. That is,
<strong>when you replace dependencies, your feedback cycles actually slow down</strong> because those
replacements <em>aren't what you actually ship</em>. You aren't ever seeing your code as it truly works
until you deploy and get it in front of users<sup id="note-1-link"><a href="#note-1">1</a></sup>. If you don't have good monitoring, you may not
see it work–or not!–even then.</p>
<blockquote>
<p>Mocks are like hard drugs... the more you use, the more separated from reality everything
becomes.<sup id="note-2-link"><a href="#note-2">2</a></sup></p>
</blockquote>
<hr>
<p><small><sup><a href="#note-1-link" id="note-1">1</a></sup> You can make the argument that regardless of how you test, production is still the only place
you see how your code "truly works." The slightly more nuanced story is that how "close to truth"
your tests are is a spectrum, and it's obviously advantageous to be closer to truth than not, all
else equal. We'll touch on this more below. </small></p>
<p><small><sup><a href="#note-2-link" id="note-2">2</a></sup> Thank you Lex Pattison for this <a href="https://testing.googleblog.com/2013/05/testing-on-toilet-dont-overuse-mocks.html?showComment=1369929860616#c5181256978273365658" target="_blank" rel="noreferrer noopener">fantastic quote.</a></small></p>
<h2 id="all-tests-are-integration-tests">All tests are integration tests</h2>
<p>If we think about our testing decisions in terms of value throughput (which is the only thing that
matters) instead of fixating on isolating units, we end up making very different decisions:</p>
<ol>
<li>
<p><strong>Don't replace a dependency unless you have a really good reason to</strong>. We've talked
about some good examples of when this makes sense already: heavy dependencies, like external process
integrations, in which the complexity or time justifies the expense of replacing it. Fakes, as
described above, work great here.</p>
</li>
<li>
<p><strong>Write your production code so you can reuse as much of it in tests as possible.</strong> In
particular, encapsulate your business logic, of which there is really only one correct
implementation by definition, in reusable classes with injected dependencies.</p>
</li>
</ol>
<p>By avoiding doubles at all, you've saved yourself the time of reimplementing code you've already
written and already tested. More importantly, your tests aren't lying to you; they actually provide
meaningful feedback.</p>
<p>Unit testing, if defined by isolating a test to only one class, doesn't exist. No code exists in
a vacuum. In this way, <strong>all tests are integration tests.</strong> Rather than think about unit vs
integration vs component vs end to end or whatever, I recommend sticking to Google's pragmatic
<a href="https://testing.googleblog.com/2010/12/test-sizes.html" target="_blank" rel="noreferrer noopener">small, medium, and large</a> test categorization.</p>
<p>If you're getting hives thinking about all the places bugs could lurk without isolating a unit–I
used to–ask yourself, why are we so comfortable using the standard library, or Apache commons, or
Guava, without mocking that code out too? We trust that code. Why? <strong>We trust code that has its
own tests.</strong><sup id="note-3-link"><a href="#note-3">3</a></sup></p>
<p>We can think of our own code no differently than the standard library. If we organize our code in
layers, where each layer depends on a well-tested layer beneath it, we rarely need to replace
dependencies with test doubles at all. Simply use the real thing. The bug shouldn't be <em>there</em>,
because we've tested <em>there</em>, mitigating one of the "cons" of integration.</p><div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixgcLX3x-kYakPGxPXXWpgXLITHwkjtrij4apqwtZinHkPmap3RP4e59I2zXB5PsvVUpyf-tEyWP_sRl1VJRydbISxJGCwnKoTsm1GqcylyrDDJOVRusut2Cxi8XnzHfZo4LkBnlXtaDs/s2048/app_architecture.png" style="display: block; padding: 1em 0; text-align: center; " target="_blank" rel="noreferrer noopener"><img alt="" border="0" height="600" data-original-height="2048" data-original-width="1948" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixgcLX3x-kYakPGxPXXWpgXLITHwkjtrij4apqwtZinHkPmap3RP4e59I2zXB5PsvVUpyf-tEyWP_sRl1VJRydbISxJGCwnKoTsm1GqcylyrDDJOVRusut2Cxi8XnzHfZo4LkBnlXtaDs/s600/app_architecture.png"></a></div>
<aside>This can also be visualized as a hexagon, known as a <a href="https://alistair.cockburn.us/hexagonal-architecture/" target="_blank" rel="noreferrer noopener">"hexagonal" or "ports and
adapters" architecture</a>.</aside>
<p>You will find tests at each layer may feel redundant. The scenarios will be similar or even the
same, and will exercise much of the same code, as lower-layer tests. For example, you might have a
test "places order with account in good credit standing" at the application layer invoked via the
HTTP transport, at the application services layer invoking these classes directly, and at the domain
model layer.</p>
<pre><code class="language-java">// Use your production Spring Boot configuration, but with an in-memory profile
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@ActiveProfiles("in-memory")
class ApplicationTest {
// snip...
@Autowired
InMemorySubscriptions subscriptions;
// A larger test, with broad scope and slow startup due to Spring and
// web server initialization. We're not just testing business logic,
// but particularly focused on the transport specifics and application
// wiring: JSON serialization works how we expect, the status codes are
// right, etc. These things are often dependent on Spring configuration,
// and if our tests use different Spring configuration than production,
// what are we really testing? That's why an in-memory configuration,
// which only replaces external dependencies, is crucial.
@Test
void placesOrderWithAccountInGoodCreditStanding() {
assertOk(restTemplate.postForEntity(
"/v1/orders/",
new HttpEntity<>(ImmutableMap.of("subscription", "SKU1")),
Map.class));
assertOk(restTemplate.postForEntity(
"/v1/orders/1/",
new HttpEntity<>(ImmutableMap.of("account", 1)),
Map.class));
// It's okay to directly use another layer if some important observable
// affects of an API are external.
assertThat(subscriptions.forAccount(AccountId.of(1))).hasSize(1);
}
// Other tests can also be more technical and protocol specific like
// JSON parse failure handling, or authentication protocol support, etc.
}
// Medium tests; fast but still broad.
// Requires a Spring context for security features, maybe transactions,
// or metrics, but doesn't require a web server.
@SpringJUnitConfig
class OrderApplicationServiceTest {
// snip...
@Test
void placesOrderWithAccountInGoodCreditStanding() {
var order = orderService.startOrder(Subscription.of("SKU1"))
orderService.charge(order.id(), AccountId.of(1));
assertThat(subscriptions.forAccount(AccountId.of(1))).hasSize(1);
}
}
// Small test; fast and limited to only our domain model package.
// Requires no framework.
class OrderProcessorTest {
InMemoryCreditService creditService = new InMemoryCreditService();
InMemorySubscriptions subscriptions = new InMemorySubscriptions();
OrderProcessor orderProcessor = new OrderProcessor(creditService, subscriptions);
OrderFactory orderFactory = new OrderFactory();
@Test
void processesOrderWithAccountInGoodCreditStanding() {
var order = orderFactory.startOrder();
order.addSubscription(Subscription.of("SKU1"));
orderProcessor.process(AccountId.of(1), order);
assertThat(subscriptions.forAccount(AccountId.of(1))).hasSize(1);
}
}
</code></pre>
<p>I used to fight really hard with my tests to avoid this overlap. </p>
<p>It was far more trouble than it was worth.</p>
<p>The thing is, <strong>these aren't actually that redundant when you think about it.</strong> Remember, when you
or your teammates uses some class in your application, you expect it to adhere to its contract,
period. This is what tests do: assert things implement their contracts. How they implement them
doesn't matter to your tests, and nor should it matter to you (otherwise, how can you hope to
survive in a complex code base if you have to keep the whole thing in your head?). If one of these
tests fail, yes, it's quite possible the problem is in another class instead of the one under test.
But as we discussed, you should also have tests against <em>that</em> class. And if this case is missing,
great! You found a missing test, and a bug! You wouldn't have found this bug (until production, if
at all) if you replaced the dependency with a mock, and what is the point of tests if not to
discover bugs before production?</p>
<p>I also illustrated the worst of it. In practice, tests at lower levels get much more detailed than
upper levels, thoroughly testing all branches in your domain objects, since that's where most of
your business logic is (or should be) anyway. Individual upper layers likely won't be able to reach
all those branches, and don't really need to try. As a result, you end up with a familiar test
pyramid, with lots of small, fast tests, and fewer larger, slow tests.</p><div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNa4U_kMRGS3SBwCdK6XAFos56jms_XjdHIc0Nb-67TsrNJorw51NYCoakc_zzMFcsa9SWs6MgtlR7iy2bQX1PzoatZ5zvlslN_Wy3OXqxtoFVbRGz5TJa1_jSVFLRKW6PUSwX7u5VLv8/s0/Test+pyramid.png" style="display: block; padding: 1em 0; text-align: center; " target="_blank" rel="noreferrer noopener"><img alt="" border="0" data-original-height="426" data-original-width="694" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNa4U_kMRGS3SBwCdK6XAFos56jms_XjdHIc0Nb-67TsrNJorw51NYCoakc_zzMFcsa9SWs6MgtlR7iy2bQX1PzoatZ5zvlslN_Wy3OXqxtoFVbRGz5TJa1_jSVFLRKW6PUSwX7u5VLv8/s0/Test+pyramid.png"></a></div>
<p><strong>What redundancy is left is merely a reflection of the obvious: code relies on other code.</strong> And by
definition that means when we test code, we're (re)testing other code, whether we wrote it or not,
all the time. By accepting it, you've freed yourself up to reuse an entire application of code
rather than replacing it throughout your tests, and you know your tests actually reflect
reality<sup id="note-4-link"><a href="#note-4">4</a></sup>.</p>
<hr>
<p><small><sup><a href="#note-3-link" id="note-3">3</a></sup> For further exploration of tested or "well understood" as the boundary for "unit" vs
"integration" tests, check out the legendary Kent Beck's post, <a href="https://www.facebook.com/notes/kent-beck/unit-tests/1726369154062608/" target="_blank" rel="noreferrer noopener">"Unit"
Tests?</a>.</small></p>
<p><small><sup><a href="#note-4-link" id="note-4">4</a></sup> Admittedly, the only reality is actual production, which is why testing mustn't stop at the door
of prod, but embrace it through monitoring, observability, feature flags, and the like. But there's
no reason you shouldn't try to get close to production on your laptop, especially where doing so
saves you so much time to boot.</small></p>
<h2 id="summary">Summary</h2>
<p>It's not to say mocks don't have their place, but many applications benefit from a simpler world: a
world without mocks. In fact, in the simplest world, you'd reuse all the implementations you've
already written.</p>
<p>Focus on the testing the contract of the class under test. Don't worry if the tests are somehow
redundant. If the application is otherwise well architected, that's only a matter of implementation.
What matters is that the class, in conjunction with obedient collaborators, implements its own
contract.</p>
<p>If it's an external process dependency, wrap it in a simple interface–what domain-driven designers
may call an "anti-corruption layer"–and then implement a fake for it. Write tests that run against
both the fake and the real implementation to ensure the fake is compliant. Capture common set up
scenarios in the language of your problem domain as methods on your fakes.</p>
<p>Finally, compile your fakes with your program, and put them behind configuration flags or profiles
to enable lightweight modes of execution.</p>
<p>Most of all, don't get too complacent with your usual solutions or tools. Change it up. You never
know what new world you may discover.</p>
Alec Henningerhttp://www.blogger.com/profile/00830222565415954531noreply@blogger.com0tag:blogger.com,1999:blog-2329797559500355970.post-39357792287002950572020-06-16T21:00:00.018-04:002020-06-27T13:49:15.029-04:00What the health!? Implementing health probes for highly available, self-healing, global services<p>Health probes are essential to running a highly available service, yet they are surprisingly <strong>tricky to implement without inadvertently making your uptime <em>worse</em></strong>. Even popular frameworks like Spring Boot (until recently<sup id="spring-defaults"><a href="#spring-defaults-note">1</a></sup>) used unfortunate defaults that may accidentally encourage a hurried developer to fall into some surprising traps that reduce their services' uptime.</p>
<p>Over time, my team has come up with a <strong>set of rules to avoid these traps</strong>. I'll lay them out first succinctly, and then explain them in detail along with the model we use that embodies them.</p>
<ol>
<li>Don't overload health endpoints – favor more over reuse</li>
<li>Keep logic out of monitors – put in endpoints</li>
<li>Be very conservative when determining if <strong>un</strong>healthy</li>
<li>Run health checks in background – not on demand</li>
</ol>
<p><strong>If your in a hurry</strong>, you can <a href="#summary"><strong>skip straight to the summary</strong></a>.</p>
<aside>This post may use Kubernetes terms like "container" and "probe", but these ideas can be applied to services running on any compute platform. For a quick overview of Kubernetes terms, see my short post <a href="//2017/06/Kubernetes-distilled.html" rel="noreferrer noopener" target="_blank">Kubernetes Distilled</a>.</aside>
<h2 id="model">A narrow, useful model of health</h2>
<p>In any nontrivial service, <a href="https://medium.com/@copyconstruct/health-checks-in-distributed-systems-aa8a0e8c1672" rel="noreferrer noopener" target="_blank">health is really a multi-dimensional spectrum</a>. A service degrades in immeasurable ways, and is probably degraded in several of them right now as you read this! This is why <a href="https://landing.google.com/sre/sre-book/chapters/service-level-objectives/" rel="noreferrer noopener" target="_blank">service level objectives</a> (SLOs) are so useful. They distill the chaos into relatively few user experience objectives, and user experience is ultimately what matters.</p>
<p>However, many times we still need to make a point in time, yes-or-no decision that can't be a complex aggregation of metrics like SLOs typically require<sup id="metrics-monitor"><a href="#metrics-monitor-note">2</a></sup>. In these cases, like load balancer health checks or Kubernetes probes, we can focus instead on answering more specific questions <em>about</em> health. That is, we can use a useful <a href="https://youtu.be/dnUFEg68ESM?t=1880" rel="noreferrer noopener" target="_blank">model</a> of health instead of a realistic one.</p>
<p>This model consists of the following <strong>5 kinds of health resources</strong> (or "queries", or "remote procedures", or "endpoints") your service can provide for use by clients like Kubernetes, monitoring software, load balancers, yourself, peer services, etc.</p>
<dl>
<dt>Readiness</dt>
<dd>Is this server warmed up, fully loaded, and "ready" to serve traffic?</dd>
<dt>Liveness</dt>
<dd>Does this server need to be killed and recreated?</dd>
<dt>Health</dt>
<dd>Is this server able to serve ANY significant traffic successfully?</dd>
<dt>Diagnostics</dt>
<dd>What's the full state of the server's ability to serve requests and its dependencies' health?</dd>
<dt>Smoketests</dt>
<dd>How do some significant use cases work from a particular call site?</dd>
</dl>
<p>To answer most of these questions, we take a <a href="https://landing.google.com/sre/sre-book/chapters/monitoring-distributed-systems/#black-box-versus-white-box-q8sJuw" rel="noreferrer noopener" target="_blank">white-box</a> approach, using explicitly defined <var>checks</var> which either pass or fail. This means we have to think about how our process works, and some of the known ways that it can degrade. We will need to note external dependencies in particular, both because we rely on them so heavily (e.g. your database), and because using them relies on a network, which is orders of magnitude less reliable than a syscall or interprocess call on the same machine.</p>
<h3 id="checks">Health checks</h3>
<p>The checks that compose the resources can be differentiated in three dimensions:</p>
<ol>
<li>The <strong>criticality</strong> of the dependency it is checking</li>
<li>The <strong>depth</strong> of health we will check</li>
<li>The <strong>timing</strong> of the check</li>
</ol>
<h4 id="criticality">Criticality</h4>
<p>The impact of a fault in a dependency depends on how critical the dependency is to our service's value. If the dependency is critical, then without it we know to take some kind of action (like serve an error page, or route to another data center). For this reason we break criticality down into two flavors:</p>
<dl>
<dt>Hard dependencies</dt>
<dd>Think: your database. Dependencies which are in at least some way <em>essential</em> for the <em>entirety</em> of your service's function. Without just one of these dependencies, your service is <em>worthless</em>. It might as well be completely down, and taking it down is likely preferable to even trying to do any work without one of these dependencies functioning in the ways you need it. If it's required for some requests but not others, and you care about those "others", then it's not a hard dependency.</dd>
<dt>Soft dependencies</dt>
<dd>Dependencies which are <em>non-essential</em>. They may still be important, but you can at least do <em>something</em>–provide <em>some</em> value–without these. A full outage would still be worse than losing one of these.</dd>
</dl>
<aside>Health checks aside, strive to make all dependencies <em>soft</em> dependencies. Your uptime is capped by your <em>hard</em> dependencies. If every request <em>requires</em> your database, and it is up 99.9% of the time, no amount of cleverness on yor part will make your service's uptime any better than 99.9%. If every request requires two external dependencies, both with 99.9% uptime, the effect is multiplicative: you will never beat 99.8% uptime. Refactoring towards soft dependencies is easier said than done, but this may involve simplifying your architecture, caching responses, using fallbacks, moving some work to be done asynchronously, etc. Topics for another day however.</aside>
<h4 id="depth">Depth</h4>
<p>The depth of a check is how coupled the check is to the useful functionality of the dependency. There are two broad classes of health <var>depths</var> we may check:</p>
<dl>
<dt>Connectivity (low depth)</dt>
<dd>Do not check very far; only that your process can at least <em>communicate</em> (including TLS handshake and authentication) with this dependency. It is a property of the network, your application configuration, the dependency's configuration, and its basic availability. Example: you can establish a connection to your database, but we don't know if the database is serving queries as expected.</dd>
<dt>Transitive health (high depth)</dt>
<dd>This is the health of the dependency itself: can it serve traffic from your service successfully? It is a property of both connectivity and how well the dependency is functioning. Example: a test database query quickly returns expected results.</dd>
</dl>
<p>You may be able to connect to a dependency, even if it is not transitively healthy. This means transitive health of a dependency will always be equally or less available than its connectivity. Given we must be conservative about <em>un</em>health, sometimes we only want to concern ourselves with connectivity. We'll see how this plays out below.</p>
<h4 id="timing">Timing</h4>
<p>The last dimension is about when the check is run. We start with two broad categories:</p>
<dl>
<dt>Synchronous</dt>
<dd>A synchronous check is run when the endpoint is queried, blocking until a result is determined.</dd>
<dt>Asynchronous</dt>
<dd>Asynchronous checks are run in the background, and endpoints serve the last seen result immediately.</dd>
</dl>
<p>One of our rules was to run health checks in the background (asynchronously, such as by using the <code>@Async</code> annotation in <a href="https://github.com/dropwizard/metrics/blob/release/4.1.x/metrics-healthchecks/src/main/java/com/codahale/metrics/health/annotation/Async.java" rel="noreferrer noopener" target="_blank">dropwizard</a>). There may be many health clients checking your service (e.g. load balancers), so if all of these clients add up to more replicas than your service (whether now or as they necessarily scale), all those health checks start to add up to quite a bit of traffic. If these checks are not very cheap, this can quickly escalate to hammering your service, and transitively its dependencies, with health checks. Asynchronous checks combat this problem by decoupling the timing of the check from the timing of the query: health requests return immediately, incurring negligible load, serving cached results from the last time the checks actually ran. A load balancer may still check frequently (and as such will continue to rapidly detect problems talking to a replica) and scale out independently without worry of load.</p>
<aside>At one point, out of paranoia, we had some rather expensive health checks. Additionally, we used a shared load balancing layer which needed to be heavily replicated. As a result, health checks ended up constituting a <em>majority</em> of our service load.</aside>
<p>That said, you are still checking your dependencies eventually, and so even running them in the background you can still hammer them depending on the number of <em>service</em> replicas and frequency of the background checks running on each. Fortunately, due to the timing decoupling of asynchronous checks, we may tune these frequencies freely. And hopefully your checks are cheap enough it doesn't really matter all that much (though you'd be surprised). A hybrid approach–use a cached result while valid, otherwise check synchronously–also works well.</p>
<aside>Another health check gone wrong: a seemingly basic health check which connected to an SMTP server was run in the background periodically on our pods. Due to the particular server implementation, and our particular SMTP client, the check was surprisingly expensive for the server. With so many pods each running the check themselves every 10 seconds, we repeatedly caused problems for our mail team.</aside>
<h2 id="endpoints">Implementing health endpoints</h2>
<p>Armed with this model of health checks, we go back and use it to describe the how and why behind our 5 health resources.</p>
<p>Recall the first rule is not to overload them. Don't use the same URL or RPC for readiness and liveness, or readiness and health, etc. Trying to cleverly reuse resources couples these distinct checks together. For what? Adding additional resources is trivial to do in most frameworks. Instead, optimize each for their singular intended purpose, giving each their own procedure that may be invoked separately. This better protects against traps that may hurt your users, and allows the logic of each to grow with your service without having to also reconfigure load balancers or monitors at the same time.</p>
<p>With that separation of concerns, we are also poised to follow the second rule: put logic in endpoints and out of clients. Rather than scripting complex rules or logic or behavior ("do X, then Z, if response looks like this, or this, or this, then treat as healthy, ...") inside generic tools like monitors, put the rules and behavior inside your <em>code</em>–the logic of procedures themselves. This makes the endpoints more reusable, particularly where tools are difficult or impossible (or cost $) to customize or script. Even if your fancy expensive load balancer can be scripted, coupling to those features makes it harder to be use a different load balancer tech later.</p>
<aside>Do not consider all of the details essential, this is mostly about principles. Ideally, a library would take care of the details–some are quite complex to implement without introducing their own bugs–and in fact this is what we do at Red Hat.</aside>
<h3 id="readiness">Readiness</h3>
<p>Readiness is a query particularly for Kubernetes controlled starts of containers (and by extension, pods). Rather than throwing traffic at the pod immediately, it waits until <a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/" target="_blank" rel="noreferrer noopener">all of its containers are <em>ready</em></a>.</p>
<ul>
<li><strong>DO</strong> return OK once the process is "warmed up." You can wait for lazy loading, run some smoke tests to JIT compile hot code paths, warm in memory caches, wait until <em>hard</em> dependency <em>connectivity</em> is established, and so on. This helps prevent long tail latencies after startup, and protects against a bad configuration taking down your service, respectively.</li>
<li><strong>CONSIDER</strong> performing no checks, and always returning OK, once you have first returned OK. If you're using global load balancing, we have a different resource for taking a region out of rotation.</li>
<li><strong>DO NOT</strong> check soft dependencies. This means the container may still be considered ready without them, even if the problem is misconfiguration on your end. Unfortunate, but you should allow Kubernetes to reschedule your pod at any time, and this will require your containers' readiness checks to pass. If you try to check soft dependencies, even just connectivity, you risk blocking start up for your entire service if one is down. Losing a soft dependency, as discussed above, is not fatal, but a full outage sure is. We'd like to catch misconfigurations on our end, but unfortunately it's difficult, if not <a href="https://www.google.com/search?q=%22unreliable+failure+detectors+for+reliable+distributed+systems%22" target="_blank" rel="noreferrer noopener">impossible</a>, to detect whether the failure is due to our configuration, a specific Kubernetes node, or external factors.</li>
</ul>
<h3 id="liveness">Liveness</h3>
<p>Liveness is a container self-healing mechanism. If a container is not alive, Kubernetes will restart it. Crucially, this means the scope of liveness is the container and only the container.</p>
<ul>
<li><strong>DO</strong> return NOT OK for illegal states local to the container or process: threads are deadlocked, memory is leaking/out, [container] disk is full, etc. These may all be cured be recreating the container.</li>
<li><strong>DO NOT</strong> check <em>any</em> dependencies. A restart will not help you if your database is down, and such an outage would result in <em>all</em> of your containers restarting, which might make the problem worse, or cause new problems.</li>
</ul>
<aside>It is tempting to check hard dependencies, even just connectivity. How many times has a server restart helped recover your service's connectivity to a database? Wouldn't it be nice if Kubernetes took care of the turn-it-off-and-on-again problems? It would, but if you're running at a large enough scale, the risks may outweight the benefits here. Ideally your client code is able to handle reconnecting on its own, for example, without a restart.</aside>
<h3 id="health">Health</h3>
<p>The plain "health" resource is used by global load balancers, peer services, and uptime monitors. A repeat, unhealthy (or timed out) response indicates the server is unable to serve any valuable requests. For a load balancer, this means it should not route requests to that server (which may be a virtual server representing, say, an entire region). For a peer service, it means the peer may be unhealthy itself (if this is used as a transitive health check). For an uptime monitor, it may alert someone, or track statistics for later reporting.</p>
<ul>
<li><strong>DO</strong> return NOT OK if any <em>hard</em> dependencies have failed <em>transitive health</em> checks.</li>
<li>If there are no hard dependencies, it is perfectly fine and often correct to simply do nothing and always return OK, indicating the service is likely at least running, resolvable, and reachable through the network.</li>
<li><strong>DO NOT</strong> check any <em>soft</em> dependencies. It may be tempting, but any check that relies on a globally shared failure domain may then take all regions out of rotation; in other words, no requests served instead of some requests served. This is why you must be conservative when deciding a service is <em>un</em>healthy.</li>
<li><strong>DO</strong> use the health endpoint for a basic uptime monitor and alert.</li>
</ul>
<aside>This was also learned the hard way. A critical internal service of ours health-checked based on its basic functionality. When it failed due to the outage of a dependency, the service went completely down. Unfortunately, there were other, more common code paths that would've kept working for most users... if we had only kept the nodes in rotation!</aside>
<h3 id="diagnostics">Diagnostics</h3>
<p>So far, we've looked at three resources which are surprisingly restricted, and may not really examine all that much. What if you want to look at the bigger picture: all of your dependencies, perhaps even some application configuration? Or, maybe you want to look at the state of a particular soft dependency?</p>
<p>Diagnostics fulfills this niche. Whereas the previous resources need only return an indication of pass or fail, diagnostics is just as much about rich content, intended for human operators. For example, if you monitoring shows some averse symptoms, or when testing out a new environment, you may take a quick peak at your diagnostics endpoint to see if any dependency checks are failing. You may also use it to automatically alert on known causes. For example, you could set up some alert policies for some of the soft dependencies that aren't as urgent as your SLO-based alerts (see also: <a href="https://landing.google.com/sre/sre-book/chapters/monitoring-distributed-systems/#symptoms-versus-causes-g0sEi4" rel="noreferrer noopener" target="_blank">Symptoms Versus Causes</a>).</p>
<ul>
<li><strong>DO</strong> include as much content as you'd like (such as all dependencies' health and connectivity) that is generally useful to operators.</li>
<li><strong>DO</strong> authorize access to these details, which may be sensitive.</li>
<li><strong>DO NOT</strong> include any secrets in content.</li>
<li><strong>CONSIDER</strong> a parameter which allows filtering down to specific checks or set of checks.</li>
<li><strong>CONSIDER</strong> alerting on diagnostics.</li>
</ul>
<h3 id="smoketests">Smoketests</h3>
<p>Lastly, we have smoketests, which warrant some special attention. I'm not just talking about making "smoketest" calls to your service. I'm talking about <strong>a specific endpoint that itself does smoketesting for you</strong>.</p>
<p>I use these sparingly, as I much prefer monitoring the service levels of actual users, rather than synthetic calls. However, because service levels rely on actual traffic, there are two cases where service level monitoring falls short. If you alert on a 10% error rate over 5 minutes, but you only have 50 calls in that time, it just takes 5 failed calls in 5 minutes to trip your alarm. Adding in traffic from synthetic calls helps improve your signal-to-noise ratio. Additionally, sometimes you need to monitor something that isn't available for use by actual users, such as a new region or version. When you have no traffic to look at at all, you need to generate some. Thats where these calls come in handy.</p>
<p>Now perhaps you can craft a call using only your terminal and jaunty hacker wits, but <strong>wouldn't it be easier if all you had to remember was <code>/smoketest</code></strong>? Likewise, it makes monitors like New Relic Synthetics easier (and cheaper!) to set up to continuously generate such traffic because all you need is a simple ping check instead of scripts. We can also easily filter out test traffic from our access logs or metrics. Because our code knows it's running tests, it is poised to deal with pesky side effects that happen from normal calls, such as by inserting test data that gets cleaned up in the background, sending email to a test inbox, charging a fake credit card, etc. It even helps secure our API: instead of opening up actual calls to our monitoring tools (which may have widely accessible credential storage), we can restrict it to the health endpoints. This all falls right in line with our principle of keeping logic in endpoints and out of monitors. It's cohesive, and codifies domain and operational knowledge.</p>
<p>Finally, running such smoketests a few times at startup may be a simple and pragmatic way to warm up your server process (JIT, caches, connections, etc) for readiness.</p>
<ul>
<li><strong>DO</strong> add tests for high value use cases.</li>
<li><strong>DO NOT</strong> try to be thorough. It's a lost cause. That's what service level monitoring is for.</li>
<li><strong>DO</strong> authorize calls separately from the rest of your API.</li>
<li><strong>CONSIDER</strong> running checks asynchronously if you cannot adequately authorize calls.</li>
<li><strong>CONSIDER</strong> a parameter which allows filtering to specific checks or set of checks.</li>
<li><strong>CONSIDER</strong> alerting off of smoketests.</li>
</ul>
<h2 id="summary">Summary</h2>
<p>We discussed four high level guidelines:</p>
<ol>
<li>Don't overload health endpoints – favor more over reuse</li>
<li>Keep logic out of monitors – put in endpoints</li>
<li>Be very conservative when determining if <strong>un</strong>healthy</li>
<li>Run health checks in background – not on demand</li>
</ol>
<p>Then we described a model of health based on 5 resources defined by explicit, cause-oriented checks. These checks vary on the <strong>criticality</strong> of the dependency, the <strong>depth</strong> of health checked, and the <strong>timing</strong> of the check. The health resources are summarized in the table below.</p>
<table class="health-resources">
<caption>Summary of health resources.</caption>
<thead>
<th scope="row">Resource</th>
<th scope="col">Readiness</th>
<th scope="col">Liveness</th>
<th scope="col">Health</th>
<th scope="col">Diagnostics</th>
<th scope="col">Smoketests</th>
</thead>
<tbody>
<tr>
<th scope="row">Purpose</th>
<td>Is the server done loading?</td>
<td>Should the server be restarted?</td>
<td>Is the server able to serve some traffic?</td>
<td>What's the status of everything?</td>
<td>How are real use cases working for known callers?</td>
</tr>
<tr>
<th scope="col" colspan="6">Intended user</th>
</tr>
<tr>
<th scope="row">Kubernetes</th>
<td>✔</td>
<td>✔</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">Operators</th>
<td></td>
<td></td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
</tr>
<tr>
<th scope="row">Peer services</th>
<td></td>
<td></td>
<td>✔</td>
<td>✔</td>
<td></td>
</tr>
<tr>
<th scope="row">Monitors</th>
<td></td>
<td></td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
</tr>
<tr>
<th scope="row">Load balancers</th>
<td></td>
<td></td>
<td>✔</td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="col" colspan="6">Checks</th>
</tr>
<tr>
<th scope="row">Hard dependencies</th>
<td>Connectivity</td>
<td></td>
<td>Health</td>
<td>Health</td>
<td></td>
</tr>
<tr>
<th scope="row">Soft dependencies</th>
<td></td>
<td></td>
<td></td>
<td>Health</td>
<td></td>
</tr>
<tr>
<th scope="row">Other</th>
<td>"Warmed up" (caches warmed, lazy loading done, hot code paths run (JIT), ...)</td>
<td>Illegal states local to the container (OOM, deadlocked threads, ...)</td>
<td></td>
<td>Anything you find helpful!</td>
<td>Real use cases with test data / controlled side effects</td>
</tr>
</tbody>
</table>
<p>If you found this helpful, or have questions or concerns, let me know in the comments. Thanks for reading!</p>
<hr>
<p><small id="spring-defaults-note"><a href="#spring-defaults"><sup>1</sup></a> Actuator includes all health checks in its health endpoint, which I've seen many developers use as a quick health check for load balancers or for Kubernetes probes, even though semantically it matches the <var>diagnostics</var> query described above, which is not appropriate for either. Recently Actuator gained <a href="https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-Kubernetes-probes-external-state" rel="noreferrer noopener" target="_blank">explicit Kubernetes probe support</a> which has better default behavior.</small></p>
<p><small id="metrics-monitor-note"><a href="#metrics-monitor"><sup>2</sup></a> You probably could conceive of a check which actually relied on, say, pre-aggregated metrics based on black-box, observed symptoms. This would be appropriate for weighted load balancing, and in fact <a href="https://github.com/Netflix/ribbon/wiki/Working-with-load-balancers#weightedresponsetimerule" rel="noreferrer noopener" target="_blank">some load balancers</a> do just that using in memory statistics from previous requests to each member. For layer 7 load balancers, this is not a bad approach, as they are seeing all of the traffic anyway, and monitoring actual calls captures much more subtlety than a binary up/down decision. That said, the two approaches are not mutually exclusive.</small></p>Alec Henningerhttp://www.blogger.com/profile/00830222565415954531noreply@blogger.com0Raleigh, NC, USA35.7795897 -78.63817877.469355863821157 -113.7944287 64.089823536178841 -43.4819287tag:blogger.com,1999:blog-2329797559500355970.post-90274230951535578232020-05-22T17:39:00.018-04:002020-06-12T19:17:48.128-04:00Asynchronous denormalization and transactional messaging with MongoDB change streams<p>MongoDB provides a very useful set of abstractions for building a reliable service. I've found in practice, few databases can rival its indexing, resiliency (automated failover, even between regions; sharding) and consistency (primary and secondary reads) characteristics and their corresponding client library support. Of course, for all that, you give up flexible transaction boundaries (among other things). With a traditional RDBMS, you can describe the data you need to fit your business model, and worry about isolating writes among different tables later. With document-stores like MongoDB, you must design your documents around sensible isolation boundaries and query patterns.</p>
<p>So, when joins are needed, you're typically advised to denormalize the joined data in order to keep queries to a single document, but at the same time MongoDB provides little facilities for denormalizing data!</p>
<aside>As of MongoDB 4.0, there <em>are</em> multi-document transactions, though they come at a performance and complexity cost that the algorithm described here does not. For performance, you gain isolation, although the degree of actual isolation in practice <a href="https://jepsen.io/analyses/mongodb-4.2.6">may not yet be as good as advertised</a>. This post assumes you do not need isolation between multi-document writes, or are integrating with another service, e.g. a message broker or HTTP API, which does not participate in MongoDB's transaction protocol.</aside>
<p>Enter change streams: using change streams, we can effectively create eventually consistent materialized views which reliably denormalize data by listening to when data changes, updating related documents as needed. Change streams also enable other asynchronous patterns like ordered, reliable, at-least-once <a href="https://microservices.io/patterns/data/domain-event.html">event publishing</a>, asynchronous callbacks, <a href="https://microservices.io/patterns/data/saga.html">sagas</a>, etc. They effectively allow us to build distributed transactions at the application layer. These have some of the characteristics of transactions–namely, they will eventually succeed–but we intentionally give up isolation for performance. That is, if our change listener is going to update a document B as a result of a write to document A, then queries may see B after A's write but before B's update.</p>
<p>This is easier said than done. Change streams provide the essential change data capture primitive to make this happen, but there is more work to be done to achieve the higher level abstractions described above. Fortunately, it not too difficult if you know what to watch out for.</p>
<aside>MongoDB does have a Kafka connector, and then you could use Kafka to implement similar patterns as described here. However, now you have to run a Kafka cluster. MongoDB alone provides enough for us to accomplish what we need without incuring the additional operational cost of another system.</aside>
<h2>Denormalizing in MongoDB</h2>
<p>Without change streams (or tailing the oplog), we can still try to denormalize.</p>
<aside>Tailing the oplog was a technique used prior to the availability of fit-for-purpose change streams, which abstract away some of the <a href="https://www.mongodb.com/blog/post/pitfalls-and-workarounds-for-tailing-the-oplog-on-a-mongodb-sharded-cluster">challenges of oplog tailing</a>.</aside>
<p>The problem is race conditions. Let's say on write to a document A, we look up some information from document B.</p>
<table class="time-process-operation">
<thead>
<tr>
<th>Time</th>
<th>Process</th>
<th>Operation</th>
</tr>
</thead>
<tbody>
<tr>
<td>t1</td>
<td>p1</td>
<td>Receives write of A. Looks up joined B data. Data not found.</td>
</tr>
<tr>
<td>t2</td>
<td>p2</td>
<td>Writes related B data</td>
</tr>
<tr>
<td>t3</td>
<td>p1</td>
<td>Writes to A without joined B data. <strong>Will not be consistent until another write!</strong></td>
</tr>
</tbody>
<caption>Fig 1. Race conditions with inline synchronous lookups</caption>
</table>
<p>This race occurs due to possible non-repeatable and phantom reads. If we try to also add a lookup of A while writing to B, we have a similar race: the related A document may not yet exist, or, if our update depends on the state of A, may be updated concurrently.</p>
<p>The only way you could recover in this scenario is by having a background "repair" process which periodically polls and fixes inconsistencies.</p>
<p>With change streams, we can do better. If we listen to both the writes of A and B, we will always eventually denormalize with consistent data.</p>
<table class="time-process-operation">
<thead>
<tr>
<th>Time</th>
<th>Process</th>
<th>Operation</th>
</tr>
</thead>
<tbody>
<tr>
<td>t1</td>
<td>p1</td>
<td>Write to A</td>
</tr>
<tr>
<td>t2</td>
<td>pN</td>
<td>Receives A change. Looks up joined B data, not found.</td>
</tr>
<tr>
<td>t3</td>
<td>p2</td>
<td>Write to joined B data</td>
</tr>
<tr>
<td>t4</td>
<td>pN</td>
<td>Receives B change. Looks up A, found. Updated.</td>
</tr>
</tbody>
<caption>Fig 2a. Defeated race condition with change listener</caption>
</table>
<table class="time-process-operation">
<thead>
<tr>
<th>Time</th>
<th>Process</th>
<th>Operation</th>
</tr>
</thead>
<tbody>
<tr>
<td>t1</td>
<td>p2</td>
<td>Write to joined B data</td>
</tr>
<tr>
<td>t2</td>
<td>pN</td>
<td>Receives B change. Looks up A, not found.</td>
</tr>
<tr>
<td>t3</td>
<td>p1</td>
<td>Insert A</td>
</tr>
<tr>
<td>t4</td>
<td>pN</td>
<td>Receives A change. Looks up B, found. Updated.</td>
</tr>
</tbody>
<caption>Fig 2b. Defeated race condition with change listener</caption>
</table>
<p>You can still <em>also</em> do a lookup on write if you like, as an imperfect consistency improvement.</p>
<h2>Closing change stream gaps</h2>
<p>Change streams can enable this, but there are a few problems for a production, scalable solution:</p>
<ul>
<li>There is no tracking of where you left off when your process restarts</li>
<li>Each listening process will receive all changes, but we only want each to be acted on once, and in order</li>
</ul>
<p>To workaround problems with multiple listener processes, you might be able to isolate your listener (apart from, say, the rest of your service API), and enforce a single replica via a scheduler like kubernetes. Personally, I prefer the cohesiveness of a single deployment. Additionally, if I deploy to multiple kubernetes clusters for redundancy, then kubernetes abstractions alone cannot enforce 1 listener replica globally across all clusters, nevermind if I'm not using kubernetes at all.</p>
<p>Instead, we put our listener behind a pessimistic lock. We can implement such a lock using MongoDB itself. Its <code>findAndModify</code> operation with upsert, majority write concern, and a unique index on an identifier of the locked resource will suffice.</p>
<aside>A majority write concern is important to ensure our lock is durable. If a concurrent failover occurs before the lock is replicated to a majority of nodes, then an additional listener may acquire the lock simultaneously due to <a href="https://docs.mongodb.com/manual/core/replica-set-rollbacks/">rollback</a> of a previous acquisition.</aside>
<p>Using the java client, such an upsert looks something like:</p>
<pre><code class="language-java"> final BsonDocument found = collection
.withWriteConcern(WriteConcern.MAJORITY)
.findOneAndUpdate(
and(
eq("_id", resource),
or(
// Lock is expired...
or(
eq("expiresAt", null),
not(exists("expiresAt")),
lte("expiresAt", clock.instant()),
// ...or we own the lock
eq("listenerId", listenerId))),
combine(
set("listenerId", listenerId),
set("expiresAt", clock.instant().plus(leaseTime)))),
new FindOneAndUpdateOptions()
.upsert(true));</code></pre>
<p>This will extend our lock if we currently have it, and attempt to acquire it otherwise. By using <code>findAndModify</code>, no two <code>listenerId</code>s can acquire the lock at the same time, because MongoDB isolates the "find and modify" from concurrent reads or writes. If the lock does not yet exist, no two listeners will acquire it, because the implicit unique index on <var>_id</var> will prevent more than one insert.</p>
<aside>When there is a race for the initial lock, the loser will fail with a duplicate key error.</aside>
<p>So, before listening, try to lock some resource (say a collection, for simplicity). Then, listen. While listening, keep pinging the lock to extend the lease. If you lose the lock, stop listening. There's more to it than that, but that's it at a high level. We'll come back to this.</p>
<p>The next requirement is to track where we left off when our process inevitably restarts. Simple: after we've successfully processed a change, save the change's <var>resume token</var> back to our lock document. If this update fails, then we must retry indefinitely or stop our listener (you must not progress to the next change until we've saved the resume token; this maintains ordered processing). Effectively this is a change "acknowledgement," quite analogous to the acknowledgements message brokers use to also implement at-least-once guaranteed delivery.</p>
<pre><code class="language-java"> public void commit(String resource, BsonDocument resumeToken)
throws LostLockException {
UpdateResult result = collection
// Again we need a majority write concern to protect our processed changes'
// resume tokens from rollbacks.
.withWriteConcern(WriteConcern.MAJORITY)
.updateOne(
and(
eq("_id", resource),
eq("listenerId", listenerId)),
combine(
set("expiresAt", clock.instant().plus(leaseTime)),
set("resumeToken", resumeToken)));
if (result.getMatchedCount() == 0) {
throw new LostLockException();
}
}</code></pre>
<p>Additionally, when initially acquiring the lock, read back the resume token to use when we start listening.</p>
<pre><code class="language-java"> final BsonDocument found = collection
.withWriteConcern(WriteConcern.MAJORITY)
.findOneAndUpdate(
and(
eq("_id", resource),
or(
// Lock is expired...
or(
eq("expiresAt", null),
not(exists("expiresAt")),
lte("expiresAt", clock.instant()),
// ...or we own the lock
eq("listenerId", listenerId))),
combine(
set("listenerId", listenerId),
set("expiresAt", clock.instant().plus(leaseTime)))),
new FindOneAndUpdateOptions()
.projection(include("resumeToken"))
.returnDocument(ReturnDocument.AFTER)
.upsert(true));</code></pre>
<p>We're almost good to go. The problem is our lock is fragile: we may lose it at any time without knowing. A thread or its process may pause arbitrarily, at any time, such as due to a garbage collection pause, CPU steal, hypervisor migration, or any number of reasons beyond our control. Any synchronous remote calls during change processing can also experience arbitrary delays, during which the lock may also expire. We can try to mitigate it by setting very long lease times relative to our periodic refresh, but now when our process crashes, it takes all of that time before we start doing any useful work again.</p>
<h2>Detecting lost locks</h2>
<p>Martin Kleppmann describes the solution well in his <a href="http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html">article about distributed locking</a>. The solution boils down to augmenting our approach in two ways:</p>
<ul>
<li>Processing a change must participate in the lock</li>
<li>We need a strictly monotonically increasing version number (or "fencing token") to use during processing</li>
</ul>
<aside>If you appreciate this post, I highly recommend Kleppmann's excellent book, <a href="http://dataintensive.net/">Designing Data-Intensive Applications</a>.</aside>
<p>Let's break those down a little bit. As we identified already, alone there's no way we can guarantee the work we do with the lock will aways be done while the lock is owned by our thread. So it follows that the work itself (such as an update to denormalize some data) <em>must</em> unfortunately participate in the lock algorithm. This is where our monotonically increasing value comes in.</p>
<p>In our denormalization example, give each document a version number. This number starts at 0 and increases with every write to the document (you may have such a version number anyway already to detect concurrent read-modify-write cycles with the same document). When we receive a change to document B, we update the related document A with the denormaized data <em>and also the version of B used</em>. Not only that, but when we do this update, we will query not just on the identifiers for A but for this version: for reference we'll call it <var>bVersion</var>. That is, we will only update a document that is both related to B <em>and</em> has a <var>bVersion</var> <em>less than</em> the B version we received in the change. If our lock has expired, and, crucially, another thread has taken the lock and acted on this change, the <var>bVersion</var> will be equal or greater to the <var>bVersion</var> we are currently processing. Thus, we will not update A. In those cases where our update does not match a document, we know we have lost our lock and therefore stop our listener.</p>
<table class="time-process-operation">
<thead>
<tr>
<th>Time</th>
<th>Process</th>
<th>Operation</th>
</tr>
</thead>
<tbody>
<tr>
<td>t1</td>
<td>pN</td>
<td>Write to joined B data, version 2 (v2).</td>
</tr>
<tr>
<td>t2</td>
<td>p1</td>
<td>Receives B change v2. Process pauses!</td>
</tr>
<tr>
<td>t3</td>
<td>-</td>
<td>p1's lock expires.</td>
</tr>
<tr>
<td>t4</td>
<td>p2</td>
<td>Acquires lock. Begins listening where last acknowledged, receiving B change v2.</td>
</tr>
<tr>
<td>t5</td>
<td>p2</td>
<td>Writes B v2 denormalized state to A.</td>
</tr>
<tr>
<td>t6</td>
<td>pN</td>
<td>Writes B v3.</td>
</tr>
<tr>
<td>t7</td>
<td>p2</td>
<td>Receives B v3 change, writes denormalized state to A. This succeeds because version 3 is greater than version 2.</td>
</tr>
<tr>
<td>t8</td>
<td>p1</td>
<td>Process unpauses. Attempts write of B v2 state to A. This fails because version 2 is not greater than version 3. Process stops listening.</td>
</tr>
</tbody>
<caption>Fig 3. Maintaining ordered writes with fencing token</caption>
</table>
<p>As you can see, using the fencing token, even when our lock expires out from under us, we will still eventually detect it. In this way, our lock's expiration timestamp is really a simple optimization. It's not actually effective in enforcing the lock, but it is an easy way to avoid an excessive amount of contention. The enforcement of the lock ultimately comes down to an atomic single document update-if-current; essentially, the same paradigm as commonly used in optimistic locking and multi-version concurrency control (MVCC).</p>
<p>In the above example, our process is already acting on versioned state, so we can use that version. Absent that, we can generate an independent version: a version of the lock. When we acquire the lock, increment a version, and return that along with the last acknowledged resume token.</p>
<aside>Be careful not to mix different version sources!</aside>
<p>All together, this looks like:</p>
<pre><code class="language-java"> public Optional<ListenerLock> acquireOrRefreshFor(String resource) {
try {
final BsonDocument found = collection
.withWriteConcern(WriteConcern.MAJORITY)
.findOneAndUpdate(
and(
eq("_id", resource),
or(lockIsExpired(), eq("listenerId", listenerId))),
// This uses the ability to use an aggregation pipeline in an update new
// with MongoDB 4.2 in order to conditionally increment the version. A
// simple always-incrementing counter works fine, too, it just gets
// redundantly incremented on refreshes.
singletonList(combine(
set("listenerId", listenerId),
set("version", sameIfRefreshOtherwiseIncrement()),
set("expiresAt", clock.instant().plus(leaseTime)))),
new FindOneAndUpdateOptions()
.projection(include("resumeToken", "version"))
.returnDocument(ReturnDocument.AFTER)
.upsert(true));
return Optional.ofNullable(found)
.map(it -> new ListenerLock(
it.getNumber("version"),
it.getDocument("resumeToken", null)));
} catch (MongoCommandException e) {
final ErrorCategory errorCategory = ErrorCategory.fromErrorCode(e.getErrorCode());
if (errorCategory.equals(DUPLICATE_KEY)) {
return Optional.empty();
}
throw e;
}
}
public void commit(String resource, BsonDocument resumeToken)
throws LostLockException {
UpdateResult result = collection
.withWriteConcern(WriteConcern.MAJORITY)
.updateOne(
and(
eq("_id", resource),
eq("listenerId", listenerId)),
combine(
set("expiresAt", clock.instant().plus(leaseTime)),
set("resumeToken", resumeToken)));
if (result.getMatchedCount() == 0) {
throw new LostLockException();
}
}
private Bson lockIsExpired() {
return or(
eq("expiresAt", null),
not(exists("expiresAt")),
lte("expiresAt", clock.instant())
);
}
private Document sameIfRefreshOtherwiseIncrement() {
return new Document("$cond", new Document(ImmutableMap.of(
"if", new Document("$ne", asList("$listenerId", listenerId)),
"then", new Document("$ifNull", asList(
new Document("$add", asList("$version", 1)),
0)),
"else", "$version")));
}
</code></pre>
<p>Finally, if your change processing includes other side effects, like an external API call, that call must also participate in the lock. As described above, the API must accept the fencing token, and use it while processing the request.</p>
<aside>If you'd like to publish messages as a result of these changes for consumers other than your internal logic, you can publish a message to a real bonafide message queue. If your broker has <a href="https://activemq.apache.org/components/artemis/documentation/latest/duplicate-detection.html">duplicate detection</a>, you may be able to maintain ordering by using duplicate detection with a document version or resume token as the duplicate ID. If you have experience with this, I'd love to hear abot it.</aside>
<h2>Bootstrapping the change listener</h2>
<p>If you go to use this, you may quickly find there is actually a simple race we haven't solved for yet. It starts at the beginning... the <em>very</em> beginning.</p>
<table class="time-process-operation">
<thead>
<tr>
<th>Time</th>
<th>Process</th>
<th>Operation</th>
</tr>
</thead>
<tbody>
<tr>
<td>t1</td>
<td>pN</td>
<td>First write to a watched document</td>
</tr>
<tr>
<td>t2</td>
<td>pN</td>
<td>First change listener starts. No resume token saved previously, so none used. <strong>Write in t1 will be missed!</strong></td>
</tr>
</tbody>
<caption>Fig 4a. Initial bootstrapping race condition (race on startup)</caption>
</table>
<table class="time-process-operation">
<thead>
<tr>
<th>Time</th>
<th>Process</th>
<th>Operation</th>
</tr>
</thead>
<tbody>
<tr>
<td>t1</td>
<td>p1</td>
<td>Change listener starts. No resume token saved previously, so none used.</td>
</tr>
<tr>
<td>t2</td>
<td>pN</td>
<td>First write to a watched document</td>
</tr>
<tr>
<td>t3</td>
<td>p1</td>
<td>Change listener crashes before processing t2's change.</td>
</tr>
<tr>
<td>t4</td>
<td>pN</td>
<td>New change listener starts. No resume token saved previously, so none used. <strong>Write in t2 will be missed!</strong></td>
</tr>
</tbody>
<caption>Fig 4b. Initial bootstrapping race condition (race on startup with crash)</caption>
</table>
<p>Without a resume token, our listener is starting up simply by starting listening from <em>now</em>. However there is nothing that guarantees a write didn't happen before this, and we need a way to process these writes as well.</p>
<p>When there is no resume token, it basically means that we should start from the "beginning"–the first change. However, the furthest you can go back to resume a change stream is the oldest entry in the oplog (change streams are backed by the oplog). As of MongoDB 4.0, you can start a change stream from a timestamp, however this timestamp must be in the range of the oplog. We can't just say, "start from the oldest entry in the oplog, whatever that is." This makes it tricky.</p>
<p>The oplog is just a collection in MongoDB. Above I mentioned the old "tailing the oplog" technique; reading from the oplog is an old trick. In our case, what we need is quite simple, and not as fraught as trying to use the oplog for change data capture. We just need the oldest record's <code>ts</code> field value. We can retrieve that like so:</p>
<pre><code class="language-java"> MongoDatabase local = client.getDatabase("local");
MongoCollection<Document> oplog = local.getCollection("oplog.rs");
// Filter out "no-op" operations like replicaset initialization, which we cannot
// start from
Document first = oplog.find(ne("op", "n"))
.sort(Sorts.ascending("$natural"))
.limit(1)
.first();
return Optional.ofNullable(first).map((op) -> op.get("ts", BsonTimestamp.class));</code></pre>
<p>When starting our listener, we already first reqire a lock. If we adjust this to also require <em>either</em> a resume token (<code>resumeAfter(token)</code>) <em>or</em> an oplog entry timestamp (<code>startAtOperationTime(timestamp)</code>) as shown above, we can avoid the race conditions shown in figures 4a and 4b. That is, it no longer matters when the change stream starts relative to the first write, so long as the change stream starts while that write is still within the oplog. Effectively, our listener must act a bit like another replicaset member, and so the oplog size must be large enough for it to "catch up" to "now." This is true regardless of whether we have a resume token or not to resume from.</p>
<h2>Try it out!</h2>
At Red Hat, we're using a basic version of this algorithm in one of our services. I've published <a href="https://github.com/alechenninger/roger">an evolved fork of the code on GitHub</a> demonstrating the techniques and code in this post. I hope you find the code useful in your own work.
<h2>Next steps</h2>
This works quite well if you're okay limiting an entire collection or database to a single thread. In Kafka terms, these are our "partitions." I am currently investigating how
to implement intra-collection partitions.
<h2>Summary</h2>
<ul>
<li>MongoDB is a solid choice for workloads that require multi-region high availability, assuming your business logic is stable enough for you to feel comfortable defining isolated write boundaries up front (such as when using <a href="https://martinfowler.com/bliki/AggregateOrientedDatabase.html">Domain Driven Design "aggregates"</a>).</li>
<li>Where joins are needed, we'd like to denormalize instead of using aggregation, but multi-document transactions are new, slow, and complex, and we don't need strong isolation.</li>
<li>Additionally, decoupled, asynchronous integration with peer services through event messages is an effective integration pattern.</li>
<li>Change streams provide the necessary core abstraction to build transactional denormalization and messaging that MongoDB does not provide out of the box.</li>
<li>To use change streams for these purposes reliably, we must use a lock, fencing token, and save our resume tokens after each change is processed.</li>
<li>To bootstrap the change stream listener before we have a resume token saved, we must look up the oldest entry in the oplog.</li>
<li>Check out <a href="https://github.com/alechenninger/roger">roger</a>, an open source prototype library which demonstrates these techniques.</li>
</ul>
<p>Thanks for reading!</p>
Alec Henningerhttp://www.blogger.com/profile/00830222565415954531noreply@blogger.com2tag:blogger.com,1999:blog-2329797559500355970.post-10213690417841112562017-12-29T14:23:00.001-05:002020-06-22T20:02:31.477-04:00Kubernetes Distilled, Part 1: Deployments and Services<p/>Kubernetes documentation is thorough, and you should read it when you have the time. Until then, this is a condensed overview aimed at developers using a Kubernetes platform that should get you comfortable enough, quickly enough, to have enough of a foothold to experiment and understand the official documentation where more detail is needed.
<p/>I'm not an expert, so YMMV.
<aside>I assume you are familiar with Docker, linux containers, and with the basic premise of Kubernetes: you've got images and want to run them reliably, with minimal manual intervention. As a tradeoff, you must learn and invest in Kubernetes' abstractions.</aside>
<section>
<h2>Overview</h2>
<p/><dfn>Kubernetes</dfn> (or "k8s"–which is to Kubernetes as a11y is to accessibility) runs on top of a <dfn>cluster</dfn> of <dfn>nodes</dfn>, where a node is machine as in physical or virtual machines, and a cluster is a datacenter or part of one. Nodes are the resources k8s uses to run its <a href="https://Kubernetes.io/docs/concepts/#overview">Control Plane</a> and the workloads it <dfn>schedules</dfn>. You'll interact with k8s through the control plane <a href="https://Kubernetes.io/docs/concepts/overview/Kubernetes-api">API</a>, usually from a command line client like <samp>kubectl</samp>, or from client libraries in a language of your choice.
<p/>On top of k8s, there are often services like <a href="https://www.openshift.com/">OpenShift</a> which provide yet another layer of abstraction, and can for example handle provisioning nodes and clusters running k8s for you.
</section>
<section>
<h2>Objects</h2>
<p/>K8s APIs are declarative. You do not say exactly how your application will run. Instead, you describe what your needs are in terms of objects (sometimes referred to as "resources" such as in <samp>kubectl</samp> help), each with a <dfn>kind</dfn>, a <dfn>specification</dfn> (or simply "spec"), and <dfn>metadata</dfn>. At k8s core, there is a basic, generic framework around these objects and listening to changes in its spec or <dfn>status</dfn>. Upon this framework, k8s builds its abstractions as decoupled extensions.
<p/>There are low level kinds of objects like Pods, usually managed by high level objects like Deployments. Objects can manage other objects by means of <dfn>controllers</dfn>. Controller-backed objects like Deployments and Services are usually where developers spend their time interfacing with k8s as they provide a high level of abstraction about common needs.
<p/>Specs are usually provided via the <samp>kubectl</samp> command line client and yaml files that look something like this:
<pre><code class="yaml">apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80</code></pre>
<p/>Controllers constantly watch the status and the spec of objects they manage, and try to keep them in sync. It's how your updates are recognized and how failures are recovered. For this reason you may find if you go "below" an abstraction and try to change a lower level object's spec directly, your changes may quickly be undone as k8s thinks it's "recovering" your objects that strayed from their specs. It is also technically possible to create situations where the same objects may have multiple conflicting states specified by other objects, causing controllers to constantly change their states back and forth between the differing specs.
<p/>All objects' metadata includes a <dfn>name</dfn>, lower case strings made up of alphanumerics, dashes, and dots, unique among other objects of the same type, and a <dfn>uid</dfn>, unique among all objects over the lifetime of the cluster. Name is required. Uids are provisioned automatically. Metadata requirements vary by object kind.
<p/>Most of your <samp>kubectl</samp> usage will be via the <samp>create</samp>, <samp>get</samp>, and <samp>replace</samp> subcommands which work with objects, their specs and statuses (for example <samp>kubectl get -o yaml deployments my-deployment</samp>).
</section>
<section>
<h3>Pods</h3>
<p/>A <dfn>pod</dfn> defines a single deployable unit as one or more containers that share networking and storage. This is where your code runs. A pod is to a container like a VM is to your application's process(es). Most pods will run one container, and most containers will run a single main process. Each pod gets its own IP address. Like VMs, pods are your unit of horizontal scaling: pods are <dfn>replicated</dfn> by a kind of controller, like a ReplicaSet. Unlike VMs, pods are always ephemeral: they are short lived, and they don't maintain state or their IP addresses after they are destroyed. Non-volatile, persistent storage is provided by a different object, a <dfn>PersistentVolume</dfn>. A load balanced virtual IP is provided by a <dfn>Service</dfn>.
<p/>Pods created directly are not maintained by a specific controller, so you likely will spec and create pods indirectly through templates inside other objects' specs. Templates tell controllers, like the DeploymentController (which uses a PodTemplateSpec inside a DeploymentSpec), how to define PodSpecs for pods they manage.
</section>
<section>
<h3>Deployments</h3>
<p/>Deployments accomplish deploying and updating your application as a set of containers with various resource requirements to a number of scheduled pods. Generally, your first steps into k8s will be by defining a DeploymentSpec. Technically, a Deployment manages <a href="https://Kubernetes.io/docs/concepts/workloads/controllers/replicaset/">ReplicaSets</a>, and each ReplicaSet manages its own set of Pods.
<p/>In addition to usual spec requirements (<samp>apiVersion</samp>, <samp>kind</samp>, <samp>metadata</samp>) a basic Deployment spec includes...
<dl>
<dt><samp>spec.template</samp></dt>
<dd>A <a href="https://Kubernetes.io/docs/reference/generated/Kubernetes-api/v1.9/#podtemplatespec-v1-core">PodTemplateSpec</a>, which defines the containers and volumes about a pod. A container spec includes the image to use, and the ports to be exposed, like so:
<pre><code class="yaml"> template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80</code></pre>
<p/>Changing the template will result in a <dfn>rollout</dfn>. This will create a new ReplicaSet with pods using the updated template, scale it up to the number of desired replicas, and scale down the old ReplicaSet to 0. Deployments have a <a href="https://Kubernetes.io/docs/reference/generated/Kubernetes-api/v1.9/#deploymentstrategy-v1-apps">DeploymentStrategy</a> which defaults to RollingUpdate that maintains at least 75% and at most 125% of desired replicas up at all times (rounded).
</dd>
<dt><samp>spec.selector</samp></dt>
<dd>An immutable <a href="https://Kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors"><dfn>label selector</dfn></a> that is intended for developers to group pods to be managed by a <strong>single</strong> Deployment. Multiple deployments should never select the same pod(s). Generally this will be the same as the pods' label:
<pre><code class="yaml"> selector:
matchLabels:
app: nginx</code></pre>
</dd>
<dt><samp>spec.replicas</samp></dt>
<dd>The number of pods to run ("replicas") among pods matching the selector.
<pre><code class="yaml"> replicas: 3</code></pre>
</dd>
</dl>
<p/>For more detailed configuration, see <a href="https://Kubernetes.io/docs/concepts/workloads/controllers/deployment/#writing-a-deployment-spec">Writing a Deployment Spec</a> and the <a href="https://Kubernetes.io/docs/reference/generated/Kubernetes-api/v1.9/#deployment-v1-apps">Deployment API reference</a>.
</section>
<section>
<h3>Services, Endpoints, and discovery</h3>
<p/>Deploying your application may be all you need if it does purely background work. However if your application provides a remote API, you can use a <dfn>Service</dfn> object to define a virtual IP (with resolvable domain name, if you're using KubeDNS) that load balances among the service's selected pods. A service spec selects pods the same way deployments do, via label selectors.
<p/>Under the hood, the <dfn>ServiceController</dfn> maintains an <dfn>Endpoint</dfn> which lists the IPs and ports of healthy pods with each Service. Nodes in the cluster are configured to load balance connections to the single virtual IP (called "cluster IP") among the pods, via simple round robin (at least by default).
<pre><code class="yaml">kind: Service
apiVersion: v1
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80</code></pre>
<p/>Services can be discovered using <a href="https://Kubernetes.io/docs/concepts/services-networking/service/#environment-variables">docker-style environment variables</a> or via <a href="https://Kubernetes.io/docs/concepts/services-networking/service/#dns">DNS</a>.
<p/>To get domain names, you must use KubeDNS. KubeDNS is an addon service and deployment that runs on k8s like any other, additionally configuring pods to use it for name resolution, and "watches the Kubernetes API for new Services and creates a set of DNS records for each". KubeDNS assigns a domain name with the format of <samp>"${service.name}.${service.namespace}.svc.${cluster.DNSDomain}"</samp> with an A record pointing at the cluster IP. The service name and namespace come from metadata. If no explicit namespace provided, <samp>"default"</samp> is used. The cluster <samp>DNSDomain</samp> comes from the <a href="https://github.com/Kubernetes/Kubernetes/blob/master/cmd/kubeadm/app/phases/addons/dns/manifests.go#L82">KubeDNS config map</a> (more on config maps later). The default is <a href="https://github.com/kubernetes/kubernetes/blob/master/cmd/kubeadm/app/apis/kubeadm/v1beta2/defaults.go#L30"><samp>"cluster.local"</samp></a>. With defaults, the example above would be resolvable from pods within the cluster at <samp>"nginx.default.svc.cluster.local"</samp>. Pods' DNS resolution has some additional defaults configured, so technically pods in the same namespace and the same cluster could simply use <samp>"nginx"</samp> domain name.
<p/>Services have different types. By default, the <samp>ClusterIP</samp> type is used, which does nothing more than assign a cluster IP and expose it to the cluster, but only the cluster. To expose services outside of the cluster, use the <a href="https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer"><samp>LoadBalancer</samp></a>. type. While there is a <samp>LoadBalancer</samp> type, most services will do some kind of load balancing.
</section>
<section>
<h2>Summary</h2>
<p/>To recap the basics:
<ul>
<li>Kubernetes uses a framework of "objects" with "metadata" and "specifications."
<li>Many objects are managed by "controllers" which are processes running within the Kubernetes control plane that watch objects status and specifications, automating the work necessary to keep the resources described by the objects in sync with their specifications.
<li>Your application runs as a set of containers inside replicated, ephemeral Pods. The PodSpec has which image to use and the ports to expose.
<li>You can deploy and replicate your application using a Deployment and a PodSpecTemplate.
<li>You can expose your application to other pods using a Service which creates a persistent, virtual IP routable within the cluster and, if KubeDNS is used, a domain name resolvable within the cluster's Pods.
</ul>
<p/>In part two I will likely talk about how to templatize configuration for your application and how to provide persistent storage to your pods. Please comment if there is something else you'd like a terse, useful summary of.
<p/>Thanks for reading!
</section>Alec Henningerhttp://www.blogger.com/profile/00830222565415954531noreply@blogger.com0Raleigh, NC, USA35.7795897 -78.63817870000002635.3677027 -79.28362570000003 36.1914767 -77.992731700000022tag:blogger.com,1999:blog-2329797559500355970.post-83664526237594799222014-07-14T23:46:00.001-04:002017-08-16T08:08:11.625-04:00A Case Study of Java's Dynamic Proxies (and other Reflection Bits): Selenium's PageFactory<h1 id="foreward">Foreward</h1>
<p>I spend a lot of time studying the source code of libraries that I really enjoy using. Since my day job is developing automated web UI tests at Red Hat, Selenium is one of those libraries. At the time I started writing this, I was not very familiar with Java’s reflection capabilities, nor what the heck a “Dynamic Proxy” was. The <a href="http://en.wikipedia.org/wiki/Proxy_pattern">Proxy pattern</a> is particularly powerful, and <code>java.lang.reflect</code>’s dynamic proxies provide a fairly nice way to do it. Dissecting <code>PageFactory</code> demonstrates.</p>
<p><div class="toc"><div class="toc">
<ul>
<li><a href="#foreward">Foreward</a></li>
<li><a href="#what-is-pagefactory">What is PageFactory?</a></li>
<li><a href="#charlie-and-the-page-factory">Charlie and the Page Factory</a></li>
<li><a href="#a-factory-in-chicago-that-makes-miniature-models-of-factories">A Factory in Chicago that Makes Miniature Models… of Factories</a><ul>
<li><a href="#1-instance-an-elementlocatorfactory-by-giving-it-a-searchcontext">1. Instance an ElementLocatorFactory by giving it a SearchContext</a></li>
<li><a href="#2-instance-a-fielddecorator-by-giving-it-the-elementlocatorfactory">2. Instance a FieldDecorator by giving it the ElementLocatorFactory</a></li>
<li><a href="#3-use-the-fielddecorator-to-assign-references-to-the-page-objects-webelement-fields">3. Use the FieldDecorator to assign references to the page object’s WebElement fields</a></li>
</ul>
</li>
<li><a href="#summary">Summary</a></li>
</ul>
</div>
</div>
</p>
<h1 id="what-is-pagefactory">What is PageFactory?</h1>
<p>Selenium WebDriver comes with a fancy class called <code>PageFactory</code>. It allows you to write your page object’s like this:</p>
<pre class="prettyprint prettyprinted"><code class="language-java"><span class="kwd">class</span><span class="pln"> </span><span class="typ">LoginPage</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="kwd">private</span><span class="pln"> </span><span class="typ">WebElement</span><span class="pln"> username</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">private</span><span class="pln"> </span><span class="typ">WebElement</span><span class="pln"> password</span><span class="pun">;</span><span class="pln">
</span><span class="lit">@FindBy</span><span class="pun">(</span><span class="pln">id </span><span class="pun">=</span><span class="pln"> </span><span class="str">"submitLogin"</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">private</span><span class="pln"> </span><span class="typ">WebElement</span><span class="pln"> submit</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="typ">HomePage</span><span class="pln"> login</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> usernameToType</span><span class="pun">,</span><span class="pln"> </span><span class="typ">String</span><span class="pln"> passwordToType</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
username</span><span class="pun">.</span><span class="pln">sendKeys</span><span class="pun">(</span><span class="pln">usernameToType</span><span class="pun">);</span><span class="pln">
password</span><span class="pun">.</span><span class="pln">sendKeys</span><span class="pun">(</span><span class="pln">passwordToType</span><span class="pun">);</span><span class="pln">
submit</span><span class="pun">.</span><span class="pln">click</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">HomePage</span><span class="pun">(</span><span class="pln">driver</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></code></pre>
<p>Those familiar with Selenium and the <a href="http://code.google.com/p/selenium/wiki/PageObjects">Page Object Pattern</a> will notice right away our members are not <code>By</code> types, but <code>WebElement</code>s. And by the looks of it, we can act on them straight away without having to <code>findElement</code> all over the place, despite appearing uninstantiated. And what’s that annotation? This is where the <code>PageFactory</code> automagic happens!</p>
<p>If you’d like to learn more about <code>PageFactory</code> and how to use it, check out the <a href="http://code.google.com/p/selenium/wiki/PageFactory">Selenium wiki</a>. If you’re like me and have to know what crazy Java wizardry is behind those factory gates, here’s your golden ticket…</p>
<h1 id="charlie-and-the-page-factory">Charlie and the Page Factory</h1>
<p>The magic starts when you initialize the page class via <code>PageFactory</code>’s static <code>initElements</code> method. It sprinkles reflection and dynamic proxy fairy dust on your <code>WebElement</code> fields so that rather than throwing <code>NullPointerException</code>, they do stuff. This post covers the details of that process, so that you too may wield such black magic!</p>
<p>Pass <code>initElements</code> either a <code>WebDriver</code> and a page object’s <code>.class</code>…</p>
<pre class="prettyprint prettyprinted"><code class="language-java"><span class="com">// If you want to instance a new page from test code.</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> funWithPageFactory</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="typ">WebDriver</span><span class="pln"> driver </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FirefoxDriver</span><span class="pun">();</span><span class="pln">
</span><span class="typ">LoginPage</span><span class="pln"> loginPage </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PageFactory</span><span class="pun">.</span><span class="pln">initElements</span><span class="pun">(</span><span class="pln">driver</span><span class="pun">,</span><span class="pln"> </span><span class="typ">LoginPage</span><span class="pun">.</span><span class="kwd">class</span><span class="pun">);</span><span class="pln">
</span><span class="com">// Do stuff</span><span class="pln">
</span><span class="pun">}</span></code></pre>
<p>Or a <code>WebDriver</code> and the page instance itself.</p>
<pre class="prettyprint prettyprinted"><code class="language-java"><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">LoginPage</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="com">// Elements go here...</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="typ">LoginPage</span><span class="pun">(</span><span class="typ">WebDriver</span><span class="pln"> driver</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="com">// Passing an already instantiated instance is just as cool</span><span class="pln">
</span><span class="typ">PageFactory</span><span class="pun">.</span><span class="pln">initElements</span><span class="pun">(</span><span class="pln">driver</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="com">// Methods to do stuff on elements go here...</span><span class="pln">
</span><span class="pun">}</span></code></pre>
<p>Either way, you start with the most reduced set of data you need: a <code>WebDriver</code> and some class that has <code>WebElement</code> fields (classic <a href="http://en.wikipedia.org/wiki/Dependency_injection#Constructor_injection_comparison">constructor style dependency injection</a>). These elements have optional annotations that describe how to construct a <code>By</code> instance. And as you very well know those are used by a driver to find elements. So, really, those are the three essential ingredients: a <code>WebDriver</code>, <code>WebElement</code> fields, and <code>By</code> objects. We make <code>By</code>’s from annotations, or we’ll assume them from the name of the <code>WebElement</code> fields.</p>
<p>So if you can’t use an element before finding it, when does <code>driver.findElement(by)</code> actually get called? The end result of a chain of Oompa Loompa shenanigans is that elements “find themselves” when they are called upon. Behind the scenes, <code>findElement</code> is not being called until you actually try to “do stuff” on that element. That’s the real drama of <code>PageFactory</code> and where the bulk of interesting work happens. Let’s take a look at that flow.</p>
<h1 id="a-factory-in-chicago-that-makes-miniature-models-of-factories">A Factory in Chicago that Makes Miniature Models… of Factories</h1>
<p>The following steps are a little hard to follow (perhaps because of a little pattern overload… but I won’t argue with Google). Ultimately, we need to sheath those WebElement fields with Proxy instances that implement WebElement, and in between calling your desired method on the desired element, do that <code>driver.findElement</code> call we know needs to happen in order to <em>get</em> the element we want to work with. PageFactory wraps that need in <code>ElementLocator</code>s. We’ll need a locator for each element, and so Selenium divvies out that duty to, you guessed it, <code>ElementLocatorFactory</code>.</p>
<h2 id="1-instance-an-elementlocatorfactory-by-giving-it-a-searchcontext">1. Instance an ElementLocatorFactory by giving it a SearchContext</h2>
<p></p><aside>Yes, <code>SearchContext</code>. It’s an interface that says “this class can find elements if you tell it how to find them.” Really when you’re locating elements, you’re doing <code>SearchContext.findElement(by)</code>. It just so happens that <code>WebDriver</code> implements that, and because that’s the only <code>SearchContext</code> implementation we have at the moment, we generally pass <code>ElementLocatorFactory</code> the driver in the standard <code>PageFactory</code> flow. But <code>ElementLocatorFactory</code> doesn’t depend on a <code>WebDriver</code> specifically. For instance, <code>WebElement</code> also implements <code>SearchContext</code>. You’ve already used it if you’ve ever called <code>findElement</code> on a <code>WebElement</code> instead of a <code>WebDriver</code>.</aside><p></p>
<p>An <code>ElementLocatorFactory</code> can take a <code>WebElement</code> field from the page object, combo it with that <code>SearchContext</code> (in a typical case this is the <code>WebDriver</code> we passed <code>initElements</code>), and spit out, you guessed it, <code>ElementLocator</code>s. We reference the individual fields via the reflection api, and we get actual <code>Field</code> objects that the locator factory can accept in order to create locators.</p>
<p>Here’s some code to illustrate that:</p>
<pre class="prettyprint prettyprinted"><code class="language-java"><span class="com">// ElementLocatorFactory instantiation and usage.</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> demonstrateLocatorFactory</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="typ">WebDriver</span><span class="pln"> driver </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FirefoxDriver</span><span class="pun">();</span><span class="pln">
</span><span class="com">// Some page modeled with the PageFactory pattern.</span><span class="pln">
</span><span class="typ">LoginPage</span><span class="pln"> loginPage </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">LoginPage</span><span class="pun">();</span><span class="pln">
</span><span class="com">// ElementLocatorFactory is an interface, and </span><span class="pln">
</span><span class="com">// DefaultElementLocatorFactory is Selenium's stock implementation</span><span class="pln">
</span><span class="typ">ElementLocatorFactory</span><span class="pln"> locatorFactory </span><span class="pun">=</span><span class="pln">
</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">DefaultElementLocatorFactory</span><span class="pun">(</span><span class="pln">driver</span><span class="pun">);</span><span class="pln">
</span><span class="com">// Here's the reflection bit. It just does exactly what it looks like.</span><span class="pln">
</span><span class="com">// Field types have a method to access their annotations (if any), so</span><span class="pln">
</span><span class="com">// the ElementLocator's will use the Field to get the annotations, which</span><span class="pln">
</span><span class="com">// has all the info to create a By object, as we'll discuss.</span><span class="pln">
</span><span class="typ">Field</span><span class="pun">[]</span><span class="pln"> fields </span><span class="pun">=</span><span class="pln"> loginPage</span><span class="pun">.</span><span class="kwd">class</span><span class="pun">.</span><span class="pln">getDeclaredFields</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Field</span><span class="pln"> field </span><span class="pun">:</span><span class="pln"> fields</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="com">// Assume for brevity these are all WebElement fields</span><span class="pln">
</span><span class="typ">ElementLocator</span><span class="pln"> locator </span><span class="pun">=</span><span class="pln"> locatorFactory</span><span class="pun">.</span><span class="pln">createLocator</span><span class="pun">(</span><span class="pln">field</span><span class="pun">);</span><span class="pln">
</span><span class="com">// Do stuff with the locator...</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></code></pre>
<p>So the Locator Factory creates Locators from Fields. If you look at all these interfaces (<code>SearchContext</code>, <code>ElementLocator</code>, <code>By</code><sup><a href="#fn1">1</a></sup>), you’ll start to see they look <em>really</em> similar. What’s special about an <code>ElementLocator</code> instance specifically is that it can find an element on demand without any parameters. A <code>SearchContext</code> needs a <code>By</code> to do that. A <code>By</code> needs a <code>SearchContext</code>. Like Doc Brown needs both a flux capacitor and some plutonium to travel through time, we need both a DOM context and a means-of-finding-something-in-that-context to reference an element.</p>
<p>Now, we’ve got a <code>SearchContext</code> (from the driver we passed <code>initElements</code>), but what about the <code>By</code>? Well, actually, we have that already too. More specifically, we have enough information to <em>make</em> a <code>By</code>. We have our page object, and that page object has fields, and each field has an annotation that says, “Hey this is how you make a By for me,” and if not, we assume that the name of the field is the exact id (in HTML terms) of the element we’re looking for. And so, an <code>ElementLocator</code> constructs a <code>By</code> itself, given the information attached to a field. And now we’ve got a <code>SearchContext</code> and a <code>By</code> wrapped up in this <code>ElementLocator</code> guy that we can pass around like it’s a <code>WebElement</code>… Effectively it is! That is, without the shortcomings of a direct <code>WebElement</code> reference. In order to get a <code>WebElement</code> we have to <em>find</em> it first, and if it can’t be found, we can’t get a reference to it (<code>findElement</code> would throw a <code>NoSuchElementException</code>). An <code>ElementLocator</code> on the other hand is as good as pointing to a specific element, but we can hold off on actually finding that element until we’re ready assert that that element should actually be there in the driver’s current context. An <code>ElementLocator</code> can even cache an element once it’s found it for the first time, and just reuse it on subsequent lookups.</p>
<p><strong>In summary, an <code>ElementLocatorFactory</code> takes a <code>SearchContext</code> and a <code>Field</code>, smashes them together and makes a portable <code>ElementLocator</code>.</strong> An <code>ElementLocator</code> constructs the <code>By</code> from the <code>Field</code>, looking at its annotations if it has any, and from there it has all the ingredients to reference a specific element without additional parameters. This useful feature is going to be essential in a minute.</p>
<p></p><aside id="fn1">[1] Actually, <code>By</code> is an abstract class, with a very slim partial implementation and a handful of static factories for subtypes. The method it leaves abstract is <code>findElements</code>. This is very similar to the method of the same name in the <code>SearchContext</code> and <code>ElementLocator</code> interfaces.</aside><p></p>
<h2 id="2-instance-a-fielddecorator-by-giving-it-the-elementlocatorfactory">2. Instance a FieldDecorator by giving it the ElementLocatorFactory</h2>
<p>The next type we encounter is a <code>FieldDecorator</code>. A field decorator is the thing that actually uses the <code>ElementLocatorFactory</code>, so we instantiate the decorator by passing along the locator factory in the decorator’s constructor. It’s going to need the ability to generate locators for a given field (which is what the factory does), because it has the core task of actually assigning <code>WebElement</code>s to the fields of our page object – that is, “decorating” those fields.</p>
<p></p><aside>At first I was confused about the redundancy here. We’re instantiating two objects of each their own type, one only used by the other, and only one of each ever needed throughout each page <code>initElements</code> process. The separation of these classes (the locator factory and the field decorator) is just for cleaner code. Technically they could be combined, and you could instantiate a field decorator by passing a <code>SearchContext</code>, and including the factory method inside of <code>FieldDecorator</code>. However, delegating these duties among two interfaces (and subsequently, implementations) means you can have different, independent implementations for each part of the process, and if anything, keep each class’s API a little cleaner. Reflection isn’t all that nice to look at as it is.</aside><p></p>
<h2 id="3-use-the-fielddecorator-to-assign-references-to-the-page-objects-webelement-fields">3. Use the FieldDecorator to assign references to the page object’s WebElement fields</h2>
<p>The <code>decorate</code> method of our <code>FieldDecorator</code> takes a <code>Field</code> and a <code>ClassLoader</code>. The <code>ClassLoader</code> is just what it sounds like: every Java class is loaded by “something.” To load a class is to take a class definition from some form, and spit out a Java-executable .class file: the real working bits. There are different <code>ClassLoader</code> implementations depending on the platform or source of the Java class. PageFactory will always just reuse the <code>ClassLoader</code> that our page object used. Any <code>Class<?></code> object has a <code>getClassLoader</code> method for this purpose.</p>
<p>More important is the <code>Field</code>, which the <code>FieldDecorator</code> will use to generate an <code>ElementLocator</code> for the particular field we are attempting to decorate. Cool, but how do we get a <code>Field</code> object? The <code>Class<?></code> interface also provides this facility, via <code>getDeclaredFields</code>. This is reflection. With a <code>Field</code>, you can examine its modifiers, and also set or get it for a particular instance of the class that declares the field. This is what PageFactory does, as seen here:</p>
<pre class="prettyprint prettyprinted"><code class="language-java"><span class="kwd">private</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> proxyFields</span><span class="pun">(</span><span class="typ">FieldDecorator</span><span class="pln"> decorator</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Object</span><span class="pln"> page</span><span class="pun">,</span><span class="pln">
</span><span class="typ">Class</span><span class="pun"><?></span><span class="pln"> proxyIn</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="typ">Field</span><span class="pun">[]</span><span class="pln"> fields </span><span class="pun">=</span><span class="pln"> proxyIn</span><span class="pun">.</span><span class="pln">getDeclaredFields</span><span class="pun">();</span><span class="pln"> </span><span class="com">// proxyIn is just the page </span><span class="pln">
</span><span class="com">// object being initialized.</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Field</span><span class="pln"> field </span><span class="pun">:</span><span class="pln"> fields</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="typ">Object</span><span class="pln"> value </span><span class="pun">=</span><span class="pln"> decorator</span><span class="pun">.</span><span class="pln">decorate</span><span class="pun">(</span><span class="pln">page</span><span class="pun">.</span><span class="pln">getClass</span><span class="pun">().</span><span class="pln">getClassLoader</span><span class="pun">(),</span><span class="pln">
field</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">value </span><span class="pun">!=</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
field</span><span class="pun">.</span><span class="pln">setAccessible</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">);</span><span class="pln"> </span><span class="com">// Fields accessed via reflection still </span><span class="pln">
</span><span class="com">// obey Java's visibility rules, however </span><span class="pln">
</span><span class="com">// this can be overriden by setting the </span><span class="pln">
</span><span class="com">// "accessible" flag.</span><span class="pln">
field</span><span class="pun">.</span><span class="pln">set</span><span class="pun">(</span><span class="pln">page</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">IllegalAccessException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">RuntimeException</span><span class="pun">(</span><span class="pln">e</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></code></pre>
<p>As you can see, <code>FieldDecorator.decorate(...)</code> returns an object, and we set that object as the value of the field we passed to it. What is that value? You might be able to guess at this point. Recall we instantiated the <code>DefaultFieldDecorator</code> by passing along the <code>ElementLocatorFactory</code>. So this thing knows how to make element locators, perhaps it just returned an element then based on the locator for the field? What if the element couldn’t be found at initialization?</p>
<p>Enter the proxy pattern. Instead of assigning those fields a <code>WebElement</code> directly, we assign it a “proxy” instance of a <code>WebElement</code>. That is, an object that implements the <code>WebElement</code> interface, but not by way of a conventional class. Instead, when methods are called on the proxy, that method and its arguments are passed to an intercepting method as arguments (as in <code>Method method, Object[] args</code>). That intercepting method is ours to implement by way of an <code>InvocationHandler</code>. When we implement the <code>InvocationHandler</code> interface, we implement that intercepting method. There, we can do whatever we want, provided it returns a type that complies with the method’s signature. Due to that constraint, it usually involves calling <code>invoke</code> of the original method object (say <code>click()</code>), on some <em>other</em> <code>WebElement</code> object. See where this is going? That “other” <code>WebElement</code> is the one our ElementLocator can track down independently. By implementing <code>WebElement</code> via a proxy, we defer calling <code>SearchContext.findElement</code> (and potentially throwing an exception), until we actually try to do something on that element. Magic!</p>
<p>Instantiating and implementing a <code>Proxy</code> is quite simple. Here’s a contrived example:</p>
<pre class="prettyprint prettyprinted"><code class="language-java"><span class="com">// The interface(s) that the proxy will implement governs this type.</span><span class="pln">
</span><span class="typ">WebElement</span><span class="pln"> proxyElement</span><span class="pun">;</span><span class="pln">
</span><span class="com">// java.lang.reflect.Proxy has a static method, newProxyInstance. At compile </span><span class="pln">
</span><span class="com">// time we can only say that this returns an Object type, but it's really </span><span class="pln">
</span><span class="com">// returning a new class that implements whatever interfaces we say it does.</span><span class="pln">
</span><span class="com">// So we can safely cast to WebElement.</span><span class="pln">
proxyElement </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">WebElement</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Proxy</span><span class="pun">.</span><span class="pln">newProxyInstance</span><span class="pun">(</span><span class="pln">
</span><span class="com">// We have to pass a ClassLoader here so the proxy class can be </span><span class="pln">
</span><span class="com">// defined. Recall this is why decorate accepts a ClassLoader (with </span><span class="pln">
</span><span class="com">// which we pass the page object's ClassLoader.</span><span class="pln">
pageObjectClassLoader</span><span class="pun">,</span><span class="pln">
</span><span class="com">// This is an array of Class types -- these are the interfaces that </span><span class="pln">
</span><span class="com">// this object supports, governing the cast rules and the methods we </span><span class="pln">
</span><span class="com">// have to be able to handle in our InvocationHandler. This is why we</span><span class="pln">
</span><span class="com">// can cast to WebElement.</span><span class="pln">
</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Class</span><span class="pun">[]</span><span class="pln"> </span><span class="pun">{</span><span class="typ">WebElement</span><span class="pun">.</span><span class="kwd">class</span><span class="pun">},</span><span class="pln">
</span><span class="com">// This is our invocation handler.</span><span class="pln">
handlerThatLocatesAnElement</span><span class="pun">);</span></code></pre>
<p>And this is what the <code>InvocationHandler</code> looks like that PageFactory uses, with my own comments added:</p>
<pre class="prettyprint prettyprinted"><code class="language-java"><span class="kwd">public</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">LocatingElementHandler</span><span class="pln"> </span><span class="kwd">implements</span><span class="pln"> </span><span class="typ">InvocationHandler</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">final</span><span class="pln"> </span><span class="typ">ElementLocator</span><span class="pln"> locator</span><span class="pun">;</span><span class="pln">
</span><span class="com">// Inject the thing that can lookup a specific element at a later time</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="typ">LocatingElementHandler</span><span class="pun">(</span><span class="typ">ElementLocator</span><span class="pln"> locator</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">locator </span><span class="pun">=</span><span class="pln"> locator</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">public</span><span class="pln"> </span><span class="typ">Object</span><span class="pln"> invoke</span><span class="pun">(</span><span class="typ">Object</span><span class="pln"> object</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Method</span><span class="pln"> method</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">[]</span><span class="pln"> objects</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">throws</span><span class="pln"> </span><span class="typ">Throwable</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="com">// The lazy look up!</span><span class="pln">
</span><span class="typ">WebElement</span><span class="pln"> element </span><span class="pun">=</span><span class="pln"> locator</span><span class="pun">.</span><span class="pln">findElement</span><span class="pun">();</span><span class="pln">
</span><span class="com">// This proxy also implements "WrapsElement" and must implement its single</span><span class="pln">
</span><span class="com">// method manually, like so:</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="str">"getWrappedElement"</span><span class="pun">.</span><span class="pln">equals</span><span class="pun">(</span><span class="pln">method</span><span class="pun">.</span><span class="pln">getName</span><span class="pun">()))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="kwd">return</span><span class="pln"> element</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="kwd">return</span><span class="pln"> method</span><span class="pun">.</span><span class="pln">invoke</span><span class="pun">(</span><span class="pln">element</span><span class="pun">,</span><span class="pln"> objects</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="typ">InvocationTargetException</span><span class="pln"> e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="com">// If the method that is reflectively invoked throws an exception, it's </span><span class="pln">
</span><span class="com">// rethrown as an "InvocationTargetException". We can throw the original,</span><span class="pln">
</span><span class="com">// more interesting exception by "unwrapping" it.</span><span class="pln">
</span><span class="com">// Unwrap the underlying exception</span><span class="pln">
</span><span class="kwd">throw</span><span class="pln"> e</span><span class="pun">.</span><span class="pln">getCause</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></code></pre>
<p>All of this work is encapsulated inside of the field decorator. You pass it a field and an <code>ElementLocatorFactory</code>, and it returns a proxy, which we then assign to the respective field. Tada!</p><div class="se-section-delimiter"></div>
<h1 id="summary">Summary</h1>
<p>With Java’s reflection and dynamic proxies you can,</p>
<ul>
<li>Retrieve a class’s fields and modify them at runtime</li>
<li>Pose as an implementation of an interface by intercepting method calls and implementing your own logic (ie. a proxy)</li>
</ul>
<p>And this is how PageFactory does its magic.</p>
<p>Happy reflecting!</p>Alec Henningerhttp://www.blogger.com/profile/00830222565415954531noreply@blogger.com0tag:blogger.com,1999:blog-2329797559500355970.post-26394476331972554672013-05-18T11:15:00.001-04:002020-05-21T20:56:01.082-04:00Performance comparison of JavaScript class inheritance patterns<p>"JavaScript is a beautiful and expressive language." Everybody says it, and it's true! The downside, of course, of such a flexible language is that there are 1001 ways to do everything, and not all ways are created equal. The performance (or lack thereof) of different patterns can be surprising, as well as their implementation quirks.</p>
<p>JavaScript's various patterns for object-oriented-programming comprise an especially vibrant topic. Many JavaScript developers have their own Best Way to implement JavaScript classes and inheritance. Many others will simply use existing <a href="https://developers.google.com/closure/library/">libraries</a> exist to facilitate the task. I've looked at <em>a lot</em> of different patterns, <a href="http://www.jsperf.com">analyzed their performance</a>, took into account their convenience, and came up with what I think is the simplest, most practical and <em>most performant</em> approach.</p>
<p>In other words, my Best Way. ;-)</p>
<aside>In analyzing performance, I'll be referencing <a href="http://jsperf.com/inheritance-approaches">this jsperf benchmark</a> a number of times. At first glance it's a lot to take in, fortunately you don't have to go through all of it (but it should provide some good supplementary reading to this post if you're so inclined). Just run it, and take a look at the results. The eight tests are grouped into two types: "definition" tests and "runtime" tests. The "definition" tests just define the classes, as opposed to the "runtime" tests which actually instantiate new objects and run some methods.
<br><br>Also, a lot of the work I'm doing is within the context of games in JavaScript, which is why performance is such a big deal for me. Different projects will lend themselves to compromising performance more or less, in turn for convenience and less bug potential.</aside>
<h3>Where Were They Going Without Ever Knowing <a href="http://www.youtube.com/watch?v=b0wfu3tOrtQ" title="Oh, 90's.">The Way</a>(s)</h3>
<p>I'll try and broadly categorize the most common approaches out there that I've encountered. Keep in mind the dynamics of the language are such that even within these categories, there are still many popular variations, and so these categories are by no means thorough, but for performance comparisons it won't make much of a difference.</p>
<h4>Closures</h4>
<p>Perhaps the most "old school" pattern, closures allow truly private variables and simple inheritance. In the closure method, you define a new class by defining a constructor function which defines your class properties and methods on a local object variable and returns that object, effectively creating a new instance of a class.</p>
<pre><code class="javascript">function ClassConstructor(privateValue, publicValue) {
var classObject = {};
var privateProperty = privateValue;
classObject.publicProperty = publicValue;
classObject.getPrivateProperty = function () {
return privateProperty;
};
classObject.method = function (arg) {
// Private properties are not actually properties of the object we are
// creating to represent our class, but nonetheless they are accessible
// to the instance of this class we are returning with this function
// (and ONLY to THAT instance) because of the closure. Refer to them in
// methods just with their names.
privateProperty += arg;
// Refer to public properties with the 'this' keyword.
this.publicProperty += arg;
};
return classObject;
}
</code></pre>
<p>And so, it's simple to create a new instance of a class, and the rest works as expected.</p>
<pre><code class="javascript">var instance = ClassConstructor('hidden', 'public');
instance.method('argument');
// Returns undefined!
console.log(instance.privateProperty);
// Returns 'publicargument'
console.log(instance.publicProperty);
// Returns 'privateargument'
console.log(instance.getPrivateProperty());
</code></pre>
<p>Inheritance in the closure format is also quite simple. If you're creating a child class, call the parent class's constructor in the child class's constructor, and extend that instance with your child class's additional properties and methods.</p>
<pre><code class="javascript">function ChildClassConstructor(privateString, publicString, childClassProp) {
var childClassObject = classConstructor(privateString, publicString);
childClassObject.childClassPublicProperty = childClassProp;
childClassObject.childClassPublicMethod = function () {
this.privateProperty += childClassProp;
}
return childClassObject;
}
</code></pre>
<p>The closure method has elegantly clean code in my opinion. If you know basic JavaScript, it makes perfect sense, and of course implements private members beautifully. Additionally, the code that defines a new class runs <em>extremely</em> fast, because all you are doing is defining a function.</p>
<p>Unfortunately, that's only relevant if you're defining a lot of new classes at runtime, which will cause JIT optimizers to hate you anyway. The real upshot of having very little work to do to define class, is that there's more work to do to actually instantiate a new object of that class. <a href="http://jsperf.com/inheritance-approaches">As you can see</a>, the closure method is an awful performer compared to the other, more browser-optimized approaches.</p>
<h4>"New" School</h4>
<p>The next category I want to talk about employs the <a href="https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/new"><code class="javascript">new</code></a> operator. Browsers really like <code class="javascript">new</code> because the objects it's creating are already defined via a constructor and its <code>prototype</code> property. They know exactly how to make new objects of that type. When we create objects with the closure method, we're doing so from the ground up, defining each property and method one at a time, each time an instance is created. Intuitively, we know those objects are going to be the same because we've defined the <em>procedure</em> to make them, but browsers much prefer a sort of, "blueprint object" to copy. It's the difference between instance creation taking 1 line of code (with the <code class="javascript">new</code> operator), or any number of lines of code (with the closure method), depending on the complexity of the class.</p>
<p>Let's take a look at how to define classes via a constructor and its <code>prototype</code> property, for use with <code>new</code>.</p>
<pre><code class="javascript">// Note the name of this function is also used as the name of our class
function SomeClass(value1, value2) {
// We use 'this' because unlike our closure constructor, this function is
// going to be called with the new instance of our object as the invocation
// context. So 'this' is referring to that new instance, and we'll use it to
// define and set the value's of the properties of this class to values of
// the arguments passed to the constructor. Note that this means when you
// define properties here they are not part of the prototype, but local to
// each instance. For local properties, this is what we want.
this.property1 = value1;
this.property2 = value2;
}
// Methods, on the other hand, should be defined on the prototype property, as
// they won't be changing from instance to instance.
SomeClass.prototype.method = function (arg) {
this.property1 += arg;
this.property2 += arg;
};
</code></pre>
<p>And (drumroll), time for <code class="javascript">new</code> to do its magic! Here's how to create a new instance of our generic, "SomeClass" type.</p>
<pre><code class="javascript">var instanceOfSomeClass = new SomeClass(123, 456);</code></pre>
<p>Tada! When we use <code class="javascript">new</code> this way, two things happen. First, a new object is created with the value of the <code>prototype</code> property of SomeClass as its prototype. Then, it calls the constructor function, SomeClass, with the arguments we pass to it, and that new object as the invocation context for that function. Finally, the new instance is assigned to our <code>instanceOfSomeClass</code> variable. And this all happens <em>really</em> fast.</p>
<h4>You Are The <a href="https://www.youtube.com/watch?v=uqhJfjbNuQg" title="Stank you very much.">Prototype</a></h4>
<p>Closures implement inheritance very simply: just by making copies of objects and extending them. When we use the <code class="javascript">new</code> operator, we'll make use of JavaScript's <a href="https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Inheritance_and_the_prototype_chain">prototypal inheritance model</a>. You're probably already familiar with it, but just in case it's basically this: every object has an associated prototype object. When looking up a property or method on an object, the interpreter will first check the object for that property or method of course, but if it's not found it'll then look to its prototype. If it's <em>still</em> not found, it'll check the prototype's prototype, and so on, until it's found or it's reached the end of the prototype chain (the generic <code class="javascript">Object.prototype</code>, who's own prototype is <code class="javascript">null</code>).</p>
<aside>There is an enormous gotcha here: the <code>prototype</code> property we've been using <em>does not actually access the prototype of the object it is a member of</em>. That <code>prototype</code> property is only a property of functions, and only really relevant to constructor functions. When we use the <code class="javascript">new</code> operator with those functions, the object created then uses that property as its actual prototype. So, I repeat, the <code>prototype</code> property of a constructor is not actually the prototype of that constructor. It does not have any affect on the prototype chain of that constructor function (which is itself, an object, of course). BUT, an object created with <code class="javascript">new</code> and that constructor, will use that <code>prototype</code> property as that object's prototype. So a constructor's <code>prototype</code> property represents the actual prototype of an object created from that constructor. Whew.</aside>
<p>To create an instance of a subclass, we need the prototype chain of that instance to look up the subclass, then the parent class. An instance get's its prototype from the <code>prototype</code> property of its class's constructor, and so, we need that <code>prototype</code> property to have it's own prototype, and it should be the <code>prototype</code> property of it's parent class's constructor. This is analogous to how we implemented inheritance with closures. Essentially, we'll start with a previous object, and extend it. Except in this case we're effectively dealing with prototypes: we start with a parent class's prototype, and then extend it.</p>
<p>Referencing a method or property of an instance of the child class will check out that object first, then it's prototype (the child class), and then the its prototype's prototype (the parent class), stopping where ever the property or method is found first. That's a mouthful. Let's check out some code.</p>
<pre><code class="javascript">// Start just as before with a normal class definition, defining properties in
// the constructor, and methods on the prototype of that constructor.
function ParentClass(x,y) {
this.x = x;
this.y = y;
}
// Now extend the prototype *property* of the constructor with some methods.
ParentClass.prototype.add = function (x,y) {
this.x += x;
this.y += y;
};
// Now let's define the constructor of our child class.
function ChildClass(x,y,z) {
// Okay, so when a new instance of ChildClass is created it's going to have
// the prototype chain taken care of, but what about the constructors?
// There's still relevant properties and initialization to take care of
// there! Welp, it's less than glamorous, but we just have to call it
// ourselves, using 'this' as the invocation context. Remember,
// constructors are called with the new 'instance' of the class as the
// invocation context, so with 'this' we're just passing that along with the
// relevant arguments.
ParentClass.call(this, x, y);
// Initialize the properties new to the child class
this.z = z;
}
// Now here's where we inherit the prototype of the parent class. Notice we use
// 'new' here because we want to modify the actual prototype of the prototype
// property. Right now the only mechanism we have to do that is with 'new',
// which returns a new object with the prototype equal to the operand's
// (parentClass's) prototype property.
ChildClass.prototype = new ParentClass();
// Now, this overwrites something important to us. That prototype property has a
// 'constructor' property. Actually, every object does. And 'new' uses it. When
// we overwrite the prototype property entirely this way, we're also overwriting
// the constructor property that would have been === ChildClass. No workaround
// but to fix it manually.
ChildClass.prototype.constructor = ChildClass;
// Now we can extend the prototype as normal, overriding parentClass methods
// with new ones of the same name (to be found first in the prototype chain),
// or additional, unique methods for the ChildClass.
ChildClass.prototype.add = function (x, y, z) {
this.z += z;
// In methods, calling the parent class's version works the same way as in
// the constructor.
ParentClass.prototype.add.call(this, x, y);
};
</code></pre>
<p>Alright! We have fully functioning object-oriented approach for JavaScript, complete with inheritance, that is <em>very fast</em>. Now for the finale.</p>
<h3>My Way Can Beat Up Your Way</h3>
<p>Okay, we're just about done, but let's take a look at a few issues with our latest approach, and get to the specifics of my Way.</p>
<p>First off, when we are creating an object with <code class="javascript">new</code>, we know that this calls the constructor function, in addition to spawning a new object with a prototype equal to that constructor's <code>prototype</code> property. When we are setting up the <code>prototype</code> property of a child class's constructor, we want the object with the right prototype, but we don't want to call that constructor. We didn't even pass any arguments. And what arguments would we pass at that point? Worse, if you have some heavy intialization code in your parent class's constructor that does more than just assign those arguments to properties, it probably won't even work at all.</p>
<p>Luckily, there's a really easy way around this for modern browsers (>IE8). Instead of using <code class="javascript">new parentConstructor()</code> use <code class="javascript">Object.create(parentConstructor.prototype)</code>. This does the same exact thing as <code class="javascript">new</code>, except <em>it doesn't call the constructor function, and it accepts the object-to-be-used-as-a-prototype as an argument directly, instead of a constructor function</em>. It can also do some <a href="https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create">fancy stuff with a second argument</a>, though it's not really relevant to this post. The only downside is that <code class="javascript">Object.create</code> is about half as fast as <code class="javascript">new</code>, but since it's only called once per subclass <em>definition</em>, I don't imagine that ever outweighing its benefits.</p>
<p>Secondly, we can abstract away some of the details for ourselves by making a <a href="http://en.wikipedia.org/wiki/Syntactic_sugar">sugar</a> function to create a child class.</p>
<pre><code class="javascript">function createChildClass(Child, Parent) {
// What we just talked about.
Child.prototype = Object.create(Parent.prototype);
// Now let's add a reference to the parent class's prototype property to the
// Child class for easy referencing.
Child._parent = Parent.prototype;
// Overwrite the constructor property as before.
Child.prototype.constructor = Child;
}
</code></pre>
<p>Usage:</p>
<pre><code class="javascript">function ChildClass (x, y, z) {
// Use our _parent property defined as a property of ChildClass. Save some
// characters and time. Notice because _parent refers to the prototype
// property of the constructor and not the constructor of the class itself,
// we have to explicitly look up the constructor like so.
ChildClass._parent.constructor.call(this, x, y);
this.z = z;
}
// This makes the magic happen.
createChildClass(ChildClass, ParentClass);
// Extend the prototype property...
ChildClass.prototype.method = function (...) {
// Do stuff
// Call the parent method
ChildClass._parent.method.call(this, ...);
};
</code></pre>
<h3>"No, Your Way Can't Beat Up My Way"</h3>
<p>Well, maybe. My Way will outrun yours, though. There are a gazillion features that you could implement from here, yes, but the thing is, anything extension or tweak from here is almost definitely going to perform significantly worse (aside from adding static private members, which is easy and very performant via a closure around the methods defined on a <code>prototype</code> property). Now, performance might not be a huge deal depending on your project, say if you're not instantiating classes very often. If that's the case, there are definitely some excellent features you can add that will help prevent bugs and abstract away even more of the implementation details. But, if you're looking for performance, and <a href="http://wiki.ecmascript.org/doku.php?id=strawman:maximally_minimal_classes">ECMAScript 6 classes</a> aren't <a href="http://kangax.github.io/es5-compat-table/es6/">yet implemented</a>, <em>my</em> Way is Best ;-).</p>
<aside>Further reading:
<ul>
<li><a href="http://developer.mozilla.org/en-US/docs/JavaScript/Introduction_to_Object-Oriented_JavaScript">Introduction to Object-Oriented JavaScript</a></li>
<li><a href="http://stackoverflow.com/questions/1595611/how-to-properly-create-a-custom-object-in-javascript/1598077#1598077">How to properly create a custom object in JavaScript</a></li>
<li><a href="https://code.google.com/p/closure-library/source/browse/closure/goog/base.js#1489">Google's Closure Library Way</a></li>
<li><a href="http://javascript.crockford.com/prototypal.html">Crockford's Way</a></li>
<li><a href="http://ejohn.org/blog/simple-javascript-inheritance/">Resig's Way</a></li>
</ul>
Various messy and undocumented benchmarks I used:
<ul>
<li><a href="http://jsperf.com/object-create-vs-crockford-vs-jorge-vs-constructor/42">Class instantiation</a></li>
<li><a href="http://jsperf.com/getprototypeof-vs-proto">Looking up prototypes / constructors</a></li>
<li><a href="http://jsperf.com/calling-superclass-methods">Methods of referring to a parent class member</a></li>
<li><a href="http://jsperf.com/subclassing-via-object-create-or-new">Object.create vs new</a></li>
<li><a href="http://jsperf.com/class-properties-via-constructor-this-or-via-prototype">Defining properties on a constructor.prototype vs within the constructor</a></li>
<li><a href="http://jsperf.com/methods-in-a-constructor-vs-prototype">Various approaches to defining methods and implementing private and static variables</a></li>
</ul>
</aside>Alec Henningerhttp://www.blogger.com/profile/00830222565415954531noreply@blogger.com0