Thursday, July 30, 2015

Some laws do not change (how i learned to stop worrying and love micro services)



Software is changing at a sickening speed. Only a few years ago, we were thrilled that Spring saved us from the bureaucratic EJB, now a 3-tier Spring/Hibernate/Relational DB application is considered classic (euphemism for old).

Luckily, some ideas in software never grow old. I do not like pitting micro service against monolithic, because we have long learned our lessons and evolved from writing monolithic applications. At the lowest level, we use object-oriented ideas to define objects, we assemble objects into components, and we assemble components into an application. At every level, we try to adhere to the basic abstraction rule: loose coupling with tight cohesion.  With this rule comes heuristics, such as: single responsibility - one abstraction should have only one responsibility. What makes an application monolithic, compared with micro service, is in its deployment. The 3-tier classic application is typically bundled into a war and deployed to some web server. While for a micro service application, a single web page might get information from different services, each is deployed and running independently. 

As long as you live on the earth, even in the cloud, there are some basic physical laws that you can’t break. “loose coupling with tight cohesion” is one of them. A micro-service architecture helps enforce this law, but if you get it wrong, it will punish you even more. When everything is bundled in a big ball (monolithically), even with well-intended interface design, there is nothing in the language itself (e.g. java) that prevents you from sneaking around those well- intended interfaces and directly invoke things behind interfaces. Often, an architect can draw a beautifully structured design diagram with layers of boxes clearly separated and communicating through well-defined channels, but in reality, those boxes of layers might talk to each other under the radar, and you’d have to trace into code to find out those “small” talks, so the beautiful architecture diagram is deep down a big ugly smelly mess. With a micro-service architecture, because services are separate processes, you have no other choices but to talk through the well-defined channels. That is good, right? Well, if you always have to deploy multiple services all together, you are virtually having a monolithic application, the fact that this monolithic application is divided into multiple services only increase overhead.

So good micro service architecture has to embody “loose coupling with tight cohesion” so that one change inside one service won’t have domino effect on other services. How to divide services is both a functional, technical and organizational question and has to be taken seriously.

The second law that you can’t break is “Conway's law”: organizations which design systems ... are constrained to produce designs which are copies of the communication structures of these organizations. I first felt this law when I was working on a project management system. This system has resource module, time module, project module, request module etc, I thought it was strange, because it was a project management system, resource, time, project, none of these domains alone meant anything, they had to work together to accomplish any business value. Then I read about this law, and all became clear: this team was structured into resource team, time team, project team etc, so the system structure reflected the team structure perfectly. And also the system suffered from the problem described above: although these modules were supposed to have well-defined interfaces, in reality, inside code, calls were made haphazardly and over the years, the system devolved into a big mess. Late on, this team felt financial pain and had to combine teams, so time and resource teams were combined, project and portofoli teams became one team -- and you could image how the supposed interfaces of these modules became dissolved. With micro service architecture, you are still bound by conway's law. How you design services will be partly decided by organizational factors, and if communications among teams are vague, slow and chaotic, communications among services will be vague, slow, and chaotic. Managing multiple services presents many challenges, and those challenges are not only technical, but also organizational (or managerial).


  •  Service evolution

Now that teams are organized around services, they have much more freedom to evolve their services. But no service lives in an island, they have to consider how to handle dependent services. If a service marches on its evolution path without consideration of its neighbors,  the hell will break loose. Even with great technical design patterns, some form of communication, negotiation and compromise is required.
             

  • Troubleshooting

With a monolithic application, when something goes wrong, you know the culprit is that monolithic application. Ok, the culprit is some module (or modules) inside that monolithic application, so responsibility shifting, finger pointing can happen. With micro service architecture, even with good logging and monitoring systems, it is hard to pin down the culprit, and teams have to work closely together to troubleshoot.


  • Architecture safety

Services have to be good citizens. If for some reason, they malfunction (e.g. can’t handle requests, dead-lock, hang, memory exhaustion etc), they need to let their neighbors know, they shouldn’t bring their neighbors down. This requires teams understanding and negotiating usage scenarios and communication etiquettes, for instance, how many requirement at peak, how to behave in peak time.

In bad days,  I feel old, and it makes me happy to think that even in this rapid changing world, some laws still hold. 

(In my opinion, Microservice poses more management challenges, if a team can't manage monolithic software, it is going to be more challenging to manage a Microservice software, so be careful).

Tuesday, July 14, 2015

Yield out of Promise hell



In Protractor, every action on an UI element is a promise. A promise is something that happens in the future. It is easy to mix present with the future, for example:
it('test_promise', function () {
    browser.get(
'http://localhost:8080/examples/test.html')
        .
then(function() {
           
var btnEle=element(by.id("b1"));
           
for(var i=0; i<5; i++){
              
console.log("clicking at "+i+" times");
              
btnEle.click().then(function(){
                  
console.log("clicked at "+i+" times");
               })
           }
        });
})

The output from the above code is:
clicking at 0 times
clicking at 1 times
clicking at 2 times
clicking at 3 times
clicking at 4 times
clicked at 5 times
clicked at 5 times
clicked at 5 times
clicked at 5 times
clicked at 5 times
What happens is the click() action is put into the promise queue and scheduled to happen in the future, while the for loop finishes in the present. If we want click() to record the clicking times correctly, we have to use another variable to record the times: 



it('test_promise2', function () {
    browser.get('http://localhost:8080/examples/test.html')
        .then(function() {
            var btnEle=element(by.id("b1"));
            var j=0;
            for(var i=0; i<5; i++){
                btnEle.click().then(function(){
                    j++;
                    console.log("clicked at "+j+" times");
                })
            }

        });
})
 
This trick can be used to get values from an array of promises:
it('test_getAllElements', function () {

    browser.get('http://localhost:8080/examples/test.html')
        .then(function() {
            var messages=[];
            var j=0;
            var defer = protractor.promise.defer();
            element.all(by.xpath("//body")).then(function(eles) {
                for (var i = 0; i < eles.length; i++) {
                    util.getNonInputText(eles[i]).then(function (text) {
                        if (util.isDefined(text) && text.length > 0) {
                            console.log("message is:" + text);
                            messages.push(text);
                        }



                        j++;
                        if (j === eles.length) {
                            console.log("fulfilled");
                            defer.fulfill(messages);
                        }
                    });
                }

            });


            defer.promise.then(function(result){
                console.log(result);
            });
        });
})





Even something as innocent as getting the ID of an element is a promise, to get the ID, you have to get it in then():




 Util.prototype.getId = function(element){
    return element.getAttribute("id").then(function(id){
       return id;
    });
};

In my tests, on my occasions, I have to calculate IDs based on known elements, so I have to write the logic like this:

var ele=element(by.name("abc"));
util.getId(ele).then(function(id){
    var anotherEle=element(by.id(id+"_btn"));
    anotherEle.click().then(function(){
        ...
    })
})

This is certainly less intuitive than getting the id directly:

var ele=element(by.name("abc"));
var id=util.getId(ele);
var anotherEle=element(by.id(id+"_btn"));


I wanted to find a way to make writing async code as intuitive as writing sync code, this led me to Generator.Here is some code that shows the basics of Generator:
function* f() {
   
console.log('before a');
   
yield 'a';
   
console.log('before b');
   
yield 'b';
   
console.log('before c');
   
yield 'c';
   
console.log('before d');
   
return 'd';
}

var g = f();
console.log("1");
console.log(g.next());
console.log("2");
console.log(g.next());
console.log("3");
console.log(g.next());
console.log("4");
console.log(g.next());




The output is:


1
before a
{ value: 'a', done: false }
2
before b
{ value: 'b', done: false }
3
before c
{ value: 'c', done: false }
4
before d
{ value: 'd', done: true }



function* f(), * makes function f a generator function. The code inside the generator function won’t get executed until next() is called; and execution inside the generator function will stop at yield until next() is called again. 
This feature is useful in making async code into sync. Say, one line of code creates a promise, the next line of code won’t get executed until the promise is executed. Library co (https://github.com/tj/co ) does exactly that. 


With co,   I can write code in a more intuitive way. For example, I can now get the id of an element:
co(function*() {
   
var ele = element(by.name("abc"));
   
var id=yield util.getId(ele);
   
var anotherEle=element(by.id(id+"_btn"));
});

The resulting code is much more structured and readable:

 

And by the way, in Webstorm, you need to configure javascript language in order for Webstorm to support yield syntax: