Struts2 - Actions with multiple methods?

action, java, oauth, struts2

Solution

IMO that's far too much code in an action method. Actions should handle the layer between the web and the business layer, and little else. This level of coupling, particularly with hard-coded class instantiations, make it very difficult to test the action's logic in isolation.

Move essentially all that code into a service. The action is concerned only with service interaction. The framework validates the presence of `code`. The service is tested outside of Struts 2. The action is tested with a mock service.

Completely untested, but I suspect my code would look much closer to what's below. It trades one type of complexity for another, but brings multiple benefits. Each method is tightly focused and easy to read. The service calls are isolated, which allows us to test different modes of service failures. It is a granular representation of system behavior and functionality.

Action

public String execute() throws Exception {
    fbService.updateFriends(code);
    return SUCCESS;
}

FB Service

public void updateFriends(String code) {
    Token accessToken = getAccessToken(code);
    Response response = getFriends(accessToken);
    if (response.getCode() == 200) {
        processFriends(accessToken, response);
    }
}

private void processFriends(Token accessToken, Response response) {
    JSONObject json = (JSONObject) JSONSerializer.toJSON(response.getBody());
    JSONArray datos = json.getJSONArray("data");
    for (Object o : datos)  {
        JSONObject friend = (JSONObject) o;
        processFriend(friend);
    }
}

private void processFriend(Token accessToken, JSONObject friend) {
    Response response = getFriendGraph(accessToken, friend.getString("id"));
    if (response.getCode() == 200){
        PerfilContacto pcBean = new JsonParse().parseFacebookElement(response.getBody());
        pcDAO.insertarContacto(pcBean); 
    }
}

//~ Service interaction

private Response getFriends(Token accessToken) {
    return sendSignedGetRequest(PROTECTED_RESOURCE_URL, accessToken);
}

private Response getFriendGraph(Token accessToken, String id) {
    return sendSignedGetRequest("https://graph.facebook.com/" + id, accessToken);
}

private Token getAccessToken(String code) {
    return service.getAccessToken(EMPTY_TOKEN, new Verifier(code));
}

private Response sendSignedGetRequest(String url, Token accessToken) {
    OAuthRequest request = new OAuthRequest(Verb.GET, url);
    service.signRequest(accessToken, request);
    return request.send();
}

If we consider metrics, we end up with the following.

Original:

Average Function NCSS:      24.00
Average Function CCN:        6.00
Program NCSS:               25.00

Reworked:

Average Function NCSS:       3.63
Average Function CCN:        1.38
Program NCSS:               31.00

Ultimately, it means:

- The number of significant LOC didn't increase much.

- Each function is < 4 lines long (easy to read).

- Each function's cyclomatic complexity is < 2 (easy to test and reason about).

- Program reads more like what it's doing, allowing faster comprehension.

The tradeoff is that we have more methods (which shouldn't be a huge issue given any reasonable IDE or text editor), which is a type of complexity. However:

- We can stop reading at a depth of our choosing.

- We can test with much greater granularity.

- We can identify success/failure modes we might have missed.

- We can swap and/or extend functionality more easily.

Problem

I'm new in this site and this is my first question. I have to do a website, I'm using java and Struts2, but I'm new with Struts2. In my site I have to do requests to Facebook and get authenticated with OAuth. I am doing all the process (authenticate and request for protected resources) in the execute method of the action page, this process is very complex and has a lot of redirects between Facebook and my web. The other day I read this "Don't create actions with multiple methods: "execute" and the operation you want to execute (e.g. "createUser") should be enough" (from http://freeopenidea.blogspot.com.es/2010/04/struts2-best-practices.html). Most of the code could be called from another part of my site in another moment, because I do this process when I connect for the first time but I could do this (or something similar) to refresh the contacts list. 1 - Should I create a separated class (not an action) for the methods that I need and call them from the "execute" method? 2 - Should I keep the code in the action page but in methods apart from "execute"? And call this page every time I need to do some of the tasks. I don't know where to put the code (and I know, I must store de accessToken. I just paste the code to show the complexity but don't look at the correction). ``` public String execute() throws Exception{ if (code!=null){ Verifier verifier = new Verifier(code); //get the accessToken to do requests Token accessToken = service.getAccessToken(EMPTY_TOKEN, verifier); OAuthRequest requestList = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL); service.signRequest(accessToken, requestList); Response responseList = requestList.send(); if (responseList.getCode() == 200){ //I get the Friends List JsonParse parser = new JsonParse(); JSONObject json = (JSONObject) JSONSerializer.toJSON(responseList.getBody()); JSONArray datos = json.getJSONArray("data"); for (Object o : datos) {//for every friend of the list I do this JSONObject elem = (JSONObject) o; String id = elem.getString("id"); OAuthRequest requestFriend = new OAuthRequest(Verb.GET,"https://graph.facebook.com/"+id); service.signRequest(accessToken, requestFriend); Response responseFriend = requestFriend.send(); if (responseFriend.getCode() == 200){ JsonParse parserAux = new JsonParse(); PerfilContacto pcBean = parserAux.parseFacebookElement(responseFriend.getBody()); pcDAO.insertarContacto(pcBean); } } } return SUCCESS; } else return ERROR; } ```

Original source