Interfaces in microservices

Arseni Mourzenko
Founder and lead developer
176
articles
February 7, 2017
Tags: productivity 36 quality 36 microservices 2

Ob­serv­ing large in-house sys­tems which ei­ther use SOA ap­proach­es or even at­tempt to mim­ic mi­croser­vices, I con­stant­ly no­tice a pat­tern which makes those sys­tems sub-op­ti­mal and dif­fi­cult to main­tain.

A tiny Hel­lo World-style ap­pli­ca­tion can eas­i­ly have all its log­ic as a mono­lith­ic piece of code. It can in­cor­po­rate some log­ic, and even be rel­a­tive­ly well or­ga­nized with a bunch of goto. As the ap­pli­ca­tion grows, main­te­nance would be­come more and more dif­fi­cult. So the code will be split in sec­tions, usu­al­ly called func­tions. If the growth con­tin­ues, the large num­ber of func­tions be­come im­pos­si­ble to main­tain ef­fec­tive­ly, so they be­come meth­ods with­in class­es. The num­ber of class­es grow, and it be­comes nec­es­sary to or­ga­nize them with­in the name­spaces, which, in turn, are put in pack­ages. Large prod­ucts con­tain hun­dreds or even thou­sands of pack­ages, which re­quires an ad­di­tion­al lev­el of or­ga­ni­za­tion, and ser­vices are one of them.

Take a pri­vate method with­in a class. How much doc­u­men­ta­tion it needs? Since the method is hope­ful­ly short and is used by a rel­a­tive­ly small num­ber of per­sons (that is, de­vel­op­ers who work on the giv­en class), there is no need for lots of doc­u­men­ta­tion: usu­al­ly, a sim­ple com­ment and the ex­pla­na­tion of the ar­gu­ments and the re­turn val­ue is large­ly enough. In the same way, if a mis­take is made with­in the in­ter­face, it doesn't mat­ter much—it is easy to change it lat­er, and re­flect the change in the class meth­ods which call the con­cerned method. This leads us to con­clude that at low com­part­men­tal­iza­tion lev­els, in­ter­faces may be poor­ly doc­u­ment­ed and poor­ly de­signed.

When it comes to a class, the size of its in­ter­face (which is the sum of all the pub­lic meth­ods with­in the class) and the po­ten­tial cas­cad­ing im­pact of the change of this in­ter­face means that one has to de­sign this in­ter­face more care­ful­ly.

But what about pack­ages and things such as ser­vices? Here, we deal with an even larg­er in­ter­face, which may be used by dif­fer­ent teams with­in the com­pa­ny, or even by the ac­tu­al cus­tomers if the pack­age is dis­trib­uted pub­licly or if the ser­vice is opened to pub­lic, mak­ing any change very dif­fi­cult to prop­a­gate. Good de­sign and good doc­u­men­ta­tion be­come key, giv­en the way the in­ter­face is used.

The com­part­men­tal­iza­tion of code through meth­ods, class­es, name­spaces, pack­ages and ser­vices has a bunch of lim­i­ta­tions, in­clud­ing the fact that a change with­in a com­part­ment could ne­ces­si­tate the change of its in­ter­face. Let's ex­plain it through an ex­am­ple. An e-com­merce web­site has a name­space which han­dles prod­uct data. Each prod­uct has an unique iden­ti­fi­er stored as smallint, which means that a max­i­mum of 32,767 prod­ucts can be stored in data­base. The web­site scales, and busi­ness needs to store hun­dreds of thou­sands of prod­ucts; more­over, it is deemed un­prac­ti­cal to have auto-in­cre­ment­ed iden­ti­fiers, and busi­ness de­cides to go for glob­al­ly unique IDs which in tech­ni­cal terms means us­ing UUIDs. This type change af­fects not only the pack­age which ac­cess­es the prod­ucts table, but also its in­ter­face, and, as a di­rect con­se­quence, the pack­ages which rely on it. A type­less in­ter­face could pre­vent such change to prop­a­gate to the callers.

This leads us to a no­tion of the ease of prop­a­ga­tion of changes. If an in­ter­face is trans­par­ent, this means that changes with­in the in­ter­faced code will of­ten lead to changes with­in the in­ter­face it­self and prop­a­gate to oth­er com­po­nents. An opaque in­ter­face, how­ev­er, will be more re­silient to change with­in the in­ter­faced com­po­nent.

What makes an in­ter­face trans­par­ent or opaque? There are two fac­tors which shift the opac­i­ty of an in­ter­face, act­ing from the one or the oth­er side of the in­ter­face:

As you can guess, opaque in­ter­faces are es­sen­tial at ser­vices lev­el. How­ev­er, when I ob­serve large in-house sys­tems (es­pe­cial­ly the ones which try to look like mi­croser­vices), in­ter­faces are rather trans­par­ent in both di­rec­tions: they change when the ab­stract­ed sys­tem changes, and they change when the caller's needs evolve. A ma­jor rea­son is that both the caller and the callee are de­vel­oped by the same com­pa­ny, and are used in pair. This leads to a dif­fi­cul­ty of putting a de­cent lev­el of ab­strac­tion, giv­en that both sides are with­in reach. The re­sult­ing volatil­i­ty of the in­ter­face makes it dif­fi­cult to prop­er­ly doc­u­ment (and keep the doc­u­men­ta­tion up to date), and even prop­er­ly ar­chi­tect it in the first place; thus, we end up with poor­ly done, cryp­tic and con­stant­ly chang­ing in­ter­faces.

Is it bad? Ab­solute­ly. The only goal of com­part­men­tal­iza­tion is to de­crease com­plex­i­ty of a sys­tem: a de­vel­op­er can fo­cus on a giv­en com­po­nent, with­out hav­ing to know the oth­er com­po­nents. A poor­ly done, cryp­tic and volatile in­ter­face pre­vents this—most de­vel­op­ers work­ing on those in-house sys­tems find them­selves work­ing on not one, but mul­ti­ple com­po­nents at the same time. There are oth­er neg­a­tive side ef­fects. For in­stance, prop­er ver­sion­ing is miss­ing in prac­ti­cal­ly every case: since in­ter­faces change too of­ten with changes prop­a­gat­ed to the callers, it's prac­ti­cal­ly im­pos­si­ble to keep up with the pace of change.

The so­lu­tion? De­sign­ing the ser­vices as if they were pub­lic (or ac­tu­al­ly make them pub­lic). If com­par­i­son can be made, at Pel­i­can De­sign & De­vel­op­ment, the ser­vices were al­ways cre­at­ed with pub­lic us­age in mind. While this has a lot of ben­e­fits in terms of se­cu­ri­ty and oth­er as­pects, it also helps fo­cus­ing on im­por­tant thing—the ser­vice in­ter­face it­self. The fact that the ser­vice is pub­lic means that I won't build it for my spe­cif­ic us­age case; in­stead, it's built con­sid­er­ing ex­clu­sive­ly the busi­ness needs of the ser­vice it­self, not one spe­cif­ic client. It also means that the ser­vice ends up with well-de­signed in­ter­face and high-qual­i­ty doc­u­men­ta­tion, which helps tremen­dous­ly my­self when I have to de­vel­op an­oth­er client six months ago.