Emma Tech

  • Emma Home
  • Emma Blog
  • Job Openings
  • RSS

Don’t fork, factor

Robert Church 6 Mar 2012 objects programming refactoring 3 Comments

There comes a time in the life­cy­cle of any sig­nif­i­cant soft­ware project when devel­op­ers will be tempted to fork the sys­tem into two dif­fer­ent prod­ucts. You might, for instance, find your­self with a cus­tomer whose require­ments seem at odds with the direc­tion of your prod­uct. You might be fac­ing some seem­ingly unre­solv­able incom­pat­i­bil­ity of sys­tem components.

At Emma, we’re in the process of rolling out a new API that will allow our cus­tomers to man­age their data in cool new ways. As part of this process, we’re migrat­ing users to a new data­base schema and adapt­ing our soft­ware to talk to the new API. In decid­ing how quickly we can move our sys­tems to sup­port both API users and those who still use our legacy data­base, we’ve faced this fork­ing ques­tion a num­ber of times.

Forking can look allur­ing. A sys­tem that only has to sup­port one kind of user sure looks sim­pler than one that has to fig­ure out what kind of user it’s work­ing for and then do the right thing. Failure to fork with­out good fac­tor­ing can result in code with ‘if’ state­ments strewn all over the place, each of them adding com­plex­ity and con­fu­sion for maintainers.

On the other hand, fork­ing pro­duces two sys­tems, both of which need to be main­tained. You’ll prob­a­bly find your­self mak­ing the same change in more than one place. It also com­pli­cates deploy­ment, and you may have to work out schemes to direct users to the right system.

Adding fea­tures to our inter­nal user admin tool to sup­port API users gave us an oppor­tu­nity to try the approach of fac­tor­ing in code that would work with legacy accounts and new accounts at the same time. We were pleased to find that we could achieve that by mak­ing a small num­ber of low risk changes to the admin tool, adding a layer of indi­rec­tion. The code actu­ally looks bet­ter than it did before the change.

Our sup­port team uses the admin tool to, among other things, restore deleted mem­bers to mail­ing lists. The old imple­men­ta­tion of this con­nects directly to the data­base and issues some SQL queries to move some records around. The new way is to issue an HTTP request to the REST API.

The code in the admin tool isn’t super nice-looking. Still, it’s served us well for many years, and we wanted to sup­port the new API with­out chang­ing the older code extensively.

So, we have some old code that looks about like this:


   class Controller(…):
        def undelete_members(account_id, user, member_ids):
            # 50 or so lines of code to execute some SQL
            # queries inside of the controller method.
            # Here be dragons.
            …

We use a sim­ple fac­tory method so that the behav­ior can be changed on a per-account basis and then move the oper­a­tions into a pair of classes that abstract the oper­a­tions on the members.


    class Controller(…):
        def undelete_members(account_id, user, member_ids):
            impl = get_backend(account_id, user)
            impl.undelete_members(member_ids)

        def get_backend(account_id, user):
            # Just the one 'if' statement
           backend_class = APIBackend \
               if is_an_api_account(account_id, user) \
               else LegacyBackend

           return backend_class(account_id, user)

    class APIBackend(object):

        def undelete_members(self, member_ids):
            res = self.call_api(
                self.account_id,
                '/members/undelete',
                method='POST',
                data=member_ids)

       class LegacyBackend(object):

           def undelete_members(self, member_ids):
               # The same 50 or so lines of code to execute
               # some SQL queries, moved out of the
               # controller. Let sleeping dragons lie.
               …

One of the inter­est­ing things about this is that, in addi­tion to wedg­ing some nice new API code into the admin tool, the legacy code gets bet­ter too. While it’s mostly untouched, so we don’t have to worry too much about intro­duc­ing sub­tle bugs, we now have a struc­ture with greater sep­a­ra­tion of con­cerns. The part of the code that deals with the incom­ing HTTP request is sep­a­rated from the part of the code that man­ages the mem­ber data. Also, in LegacyBackend, you see some­thing that looks kind of like a prim­i­tive object model. Better still, the APIBackend class looks like the begin­nings of some­thing that could be used as a client that sim­pli­fies access to the REST API in our other sys­tems, or for end users who want to man­age their own accounts in Python scripts. Everybody wins.

The moral of the story is that, with a lit­tle bit of basic old fash­ioned object inge­nu­ity, you might be able to stretch your system’s behav­ior a lot fur­ther than you think, sav­ing your­self from sup­port­ing and man­ag­ing two dif­fer­ent prod­ucts, while mak­ing your code more flex­i­ble and more maintainable.

3 comments

Scott Wainstock commented:

2012-03-06, 16:16

This guy seems pretty smart! I liked it when he blogged.

Erin Evans commented:

2012-03-14, 22:02

Howdy, tech peo­ple — when will the Salesforce.com inte­gra­tion be ready to rock? My Salesforce devel­oper is encour­ag­ing me to con­sider other email mar­ket­ing plat­forms that already have this capability…but my pref­er­ence is to wait on you guys (assum­ing you’re still on tar­get for 2012)…feedback is appre­ci­ated! :)
Thanks,
Erin

Yousaf commented:

2012-10-05, 01:35

Beautiful code!

Leave a comment

Click here to cancel reply.

About Robert Church

Robert Church

Rob is a software developer whose favorite books about programming include The Mythical Man Month by Fred Brooks and Refactoring: Improving the Design of Existing Code by Martin Fowler. He is terrible at pinball, but keeps playing it anyway.

Read more from Robert

Emma Tech on Twitter

    Follow Emma Tech »
    Help wanted

    • Popular Tags

      Python12 api7 UX5 conferences4 postgres4 workflow4 time4 javascript3 PHP3 jQuery3 tools3 editors2 travel2 server maintenance2 Git2 maintenance windows2 Haml1 Frank1 Ruby1 CSS1 PyCon1 office1 Sass1 downtime1 post‑mortems1 cgit1 books1 Trac1 collaboration1 community1 Twitter1 Facebook1 OAuth1 coding1 cool sites1 Redis1 github1 objects programming refactoring1 integration1 salesforce1 usability testing1 Social Posting1 music1 productivity1 bugs1 TextExpander1 san francisco1 Convore1 Vim1 releases1 legacy data1 HTML1 reading1 Django1 PgCon1 testing1 TDD1

    Emma is a member of the Email Sender & Provider Coalition and the Messaging Anti-Abuse Working Group.

    Copyright © 2003 - 2013 Emma.
    All rights reserved.

    • Get Emma's Newsletter
    • Visit the Emma Blog
    • @emmaemailtech on Twitter
    • @emmaemail on Twitter
    • Emma on Facebook

    Emma's email marketing makes communicating simple and stylish.
    Inquire now for more details.