Unretrofittable: Starting off right
Some abstractions are hard to (un|re)do...
Chris Winters (@cwinters) - ModCloth
Pittsburgh Techfest 2014
goo.gl/J4Tcoq
Things start out great
Greenfield projects are the best, right?
Starting a new project is one of my favorite things. Probably yours
too. People start new jobs just to do new stuff. So many things to
play with, so much to discover.
Gift to work on new! You don't know how lucky you are!
Audience Q: How many in last year? Every? New/rewritten subsystem?
It all made sense at the time
Why awesome? No usual mistakes -- troubling bugs, difficult users,
interface with terrible awful systems, compromises work org, dreadful
assumptions, hidden agendas...
But... many are good ideas at the time! Nobody wants crap -- just
willing to trade off for it.
Thing is: I will do them all. You will too. And I'll forget all these
lessons because SO EXCITING NEW SYSTEM.
Does it matter why?
Lots of reasons, that's another talk. Now: our levels of abstraction,
side effects of working with right and wrong ones.
"agile" vs AGILE
We love "agile", maybe not capital A Agile. Lot of it geared toward
making developer acutely aware of all joy + pain, tightening feedback
between awareness and influence on them.
What is your job?
Help others be awesome
...what we do is a means, not an end
? TAB Most of us don't work on software for ourselves. (Audience Q)
TAB Hard to remember because the means is fun! Argue about editors and
frameworks! But like Dustin said this AM the end is what we're here for.
Change as change does
Biology definition of "stable". We create complex systems. Not quite
life, but not bridges either.
Our main technical lever: make it easy to change.
Empathy
Change ++ surface area for empathy. Theme at devopsdays -- with
co-workers, other organization parts, but mostly with users. Last is
hard because you rarely see them, hardwired for tribes + proximity. So
anything that helps is awesome.
Frying pan?
But you probably didn't sign up for people feedback loops, right? We have
analysts and product manager to take care of these things, right?
Nope, you're FULL STACK, baby. That includes the user.
Empathy with doing, empathy with tasks
Role of our systems in user's work / life
More: what users DO. Leads to assertion: Focus on tasks as organizing
abstraction will make your system better at what it does.
Thinking about tasks user POV not only happier users, clarity on what
we should actually be focusing on.
Also: side effects
Sometimes the main benefit, sometimes a risk or something to be mitigated,
and sometimes can lead to other side effects or enable other practices.
Lever 1: Creating a usable system
One command to create a system with real(-ish) data.
How can we manifest this empathy with the user?
Seriously! Everybody ignores. Happy to limp along with data we've built up.
Show system to family: data real? real life user names? products have
generic photos? history -- was everything created the same day? what
happens when you run a report?
Hard part: real(-ish)
"But I have fixtures!"
One command is easy. Hard part is looking real. TAB Oh, you have fixtures...?
Can your domain experts edit them?
Fixtures are bare minimum. Write once read never. Relational data
without SQL is awful. In YAML files? LOL
They work table by table. Because objects mapped to tables,
data flow directly into them.
THIS SHOWS THE PROBLEM.
Abstractions shape ideas
Storage shapes how we present and think about our domain. How many
tools exist to navigate this path? How many apps have you seen that
just barf relations to user?
"They just make it easy" is problematic. People do what's easy.
So many forms
Example: hospital discharge nurse. Discharge is A Thing to her. To
you? One or two dozen tables. How to make that real?
How can you hope to rope domain experts like her into defining your
data for you?
Thinking in tasks
Users don't think in normalized tables. Don't care about
efficiency, or query planners.
Do care about duplication, but less than you think. Do care about
completeness, doing very little mapping their brain to The System.
Our job to mediate between tasks and metal. But we focus on metal. Should focus on
tasks. Metal is what we read, argue about, school for, care about.
Tasks will cause you pain
Fowler: "If it hurts, do it more often."
This path is non trivial. Even painful. But this pain is good! Data
too complex and hard to recreate give pain early. Pointer to
dissonance between user view and your view. TAB Ideal world pain will
induce change, most often we callus up.
Side effects, you
Quick startup for new hires or changing teams. Which means
people can change teams more often!
Head off model drift and too-generic data models
Side effects, others
Domain experts define and maintain data
Sales + marketing can create and customize demos
Potential users can create demos with their own data (woo cloud!)
Seriously?! There aren't many terms that evoke enterprise software
more than "auditing".
TURNS OUT auditing has great first and second-order effects to make
lots of people (including you) happy.
Why do you even?
Answer "How did this thing get this way?" at any time. Who created it
with what data, who updated it and when? Can be invaluable for support
staff, though reciting back to a user her actions may be Big Brother
Cooler than it sounds
To outsiders: auditing adds determinism. Think about how nutty our
systems look to them. Auditing helps our crazy system finally MAKE
SENSE. Not just a bunch of people guessing or shrugging and offering
no help.
More abstractions
Most frameworks hook into persistence. Attractive: leveraging tool for
orthogonal concern -- a big reason we use frameworks!
Refactoring resistant
Can work ok, but resist refactoring. How to represent *logical* record
when history is tied to *physical* record and physical storage changed?
Record tasks, not results
Traditional auditing: record results. What if we record actions
themselves? Similar to loading data: reuse those ideas and attach
'who' and 'when' and 'from where' metadata about these actions.
What could we do with that record?
Not only auditing, but replay. Replay for load, replay for modified
business logic, replay for debugging to get back to a point in
time. Maybe replay done elsewhere to run through an expensive
computation (risk profile) -- see Fowler's article on LMAX.
Other side effects
Confidence in the system: things can be explained!
Exposing intent -- RecoverPassword
vs ResetPassword
and SendPasswordRecoveryEmail
Lever 3: Undo
Lastly, we'll focus on an activity that's almost entirely end-user
focused. Undo.
Users expect small undo, not large. Undo font change or typing: just exist.
Undo order? Hospital admission? Import a school of students?
MAGIC. The sort that users will LOVE YOU for. People flip out that
they can undo gmail deletions. What if they accidentally import a
spreadsheet of 500 students to the wrong school and that can click a
button to make it better?
Again with the tasks
But they expect undo of actions, don't care about side effects in your
system. Undo should mean it's like the action never happened. The form
went in the shredder, the file never uploaded.
Couple of tricks depending on action. Both easier to do at start.
Grace period
Wait a while to do your work, don't do it if asked
I place an order, but cancel without consequence in the half hour
after. (Efficiencies in logistics can actually make this more
difficult, ask Amazon.
Simple on surface: do nothing until period up. I like doing nothing!
No free lunch
Actually: everything EXCEPT the final action. e.g., validation logic
must fire so user doesn't get "Order invalid" email half hour
later. Inventory needs to be up to date.
Tricky: validation dependent on earlier persistence. Transaction fix?
Maybe...
None of this is impossible, just easier if you have it in mind from
the beginning
Change as Commands
Represent change as commands, represent undone change as commands
Woohoo! Already user task abstraction. Great handle to UNDO
task. Commands that touch many you have to associate... but you've
already done! You want to treat it as A Thing and redisplay or enable
editing.
Side effects
Users can experiment! Less fear! So much happiness!
Lever n: Feature flags
Compile/build setting controlling feature
accessibility
Compile/build is typical, not mandated; it speaks more to their
typical scope.
Who's using them?
Why?
Timing features to releases is harder than you'd think,
particularly the older and larger your system gets. (Yes, this
is a good argument for lots of small systems. That's another
talk.)
Dependencies usually aren't this bad (crazy graph), but they can be
tricky. Feature X may require an update to a database table but
Feature Y also needs to update it, and the updates need to be done at
the same time.
BUT Feature X needs to be one two weeks before Feature Y.
Thinking differently about features
Full functionality vs not breaking
Instead of thinking of features as a discrete set of files
implementing a feature that get delievered (sorry, "merged") at once,
what if it were a set of changes that get delivered over time -- maybe
even a long time -- but none of which break existing functionality?
Not the prettiest...
Most implementations can be kind of gross... See Kellan
Elliott-McCrea's presentation on Change at Etsy for an
example. (PHP, but still...)
I'll just code that up...
You don't have to! (Flip, Setler, Togglz)
May even be able to use third party runtime A/B testing services
for lightweight version.
But I want clean!
Having part of a feature's code released is okay! (Koan: is a
feature even partially released if it cannot be used?)
Side effects++
Release more often; A/B testing
Releasing more often is good in and of itself. But it also means
you're releasing smaller changes, which are generally less
risky. Lowered risk increases development experimentation. And
that can bubble up the organization in great ways.
Related: Kill switch
Who's using these?
You might also know me...
Runtime flag removing functionality due to
disruption, planned or not
Credit card processor went down, some backend system has
collapsed and we don't want to continue throwing traffic at it,
etc. Results can be reduced functionality, or may even be hidden
to user -- for example, orders are put into a queue and not
touched but we can catchup later.
Wouldn't read-only be nice?
Why? Enabling schema change for broken databases
This could help you work around database shortcomings (MySQL
5.5, how do substantial systems use this!?) by ensuring that you
can keep your app up during blocking schema changes.
Side effects
<discuss>What actually happens when things
fail?</discuss>
vs
Most of the time these discussions are much better handled by
other parts of the business, not you. Giving an easily
understood hook into the ability to turn things off leads to
discussions of what we should actually do when they're off.
Others to think about
- Start production sequences at 1000 so have an easy 'this is
special' hook (id < 1000). (REI Outlet items, Larry Wall Huffman
coding)
- Avoid 'magic' values your platform might have (woe be the user with
ID 4 in a ruby system...)
- Extract metrics via API: generic performance as well as performance
specific for your app (Carol's example from devopsdays)
- Classify and contextualize errors - enable that intent you've
exposed via your user actions to people troubleshooting later
on. (YOU NEVER DO THIS LATER)
Others to think about
- Validation
- Act-as-user for internal admins and customer support
- Authen/Authz
- Schema updates
- Caching hooks everywhere
Others to think about
- Version internal dependencies to enable change (weight documentation
example)
- URI creation
- Serialization and marshalling
- Timezones!
- "It's easier to make a stream system batch than make a batch system stream." (@dehora)