1

I am currently in charge of designing the API of a new service. It started simple enough, 10 different URIs or so were allowed. I knew I was not doing the best code, but frankly I did not see a better way, since I lack experience, and since we are using AWS and API Gateway, I figured having less objects as is recommended could be some sort of "defense" for using less classes.

The method that takes an URI, breaks it down, creates a SQL query, and returns the rows as a PreparedStatement, used to be 200 lines of code. It was already big.

Now I have way more calls, way more cases, they want more than double the original cases thought. In the same timeframe but... The method is going to surpass 500 lines of code, and if this rate of demanding changes continues, I am worried it won't be as easily to maintain as it originally was.

The point is, without using something like Spring or Hibernate with annotations, I do not know how could I make the code more Object Oriented and properly refactor it.

Example of code:

if(pathParameters.containsValue("profile")) {
            if(numPaths == 2 && numFilters == 0) {//http:API/profile/id
                preparedstatement = con.prepareStatement("SELECT id_profile, nombre_profile "
                        + " from profile WHERE id_profile = ?;");
                preparedstatement.setString(1, pathParameters.get("nivelPath1"));
            }
            else if(numPaths == 1) {

            //[... More code....]


if(pathParameters.containsValue("skill")) {
            if(pathParameters.size() == 2 && numFilters == 0) {//http:API/skill/id
                preparedstatement = con.prepareStatement("SELECT id_skill, nombre_skill FROM skill WHERE id_skill = ?;");
                preparedstatement.setString(1, pathParameters.get("nivelPath1"));
            }
            else if (numPaths == 1) {
            if(queryParameters == null || queryParameters.isEmpty()) {//http:API/skill
                    preparedstatement = con.prepareStatement("SELECT oferta_skill_perfil.id_skill " + 
                            "FROM oferta_skill_perfil " + 
                            "INNER JOIN skill " + 
                            "ON skill.id_skill=oferta_skill_perfil.id_skill  " + 
                            "GROUP BY oferta_skill_perfil.id_skill " + 
                            "ORDER BY COUNT(oferta_skill_perfil.id_skill) DESC " + 
                            "LIMIT 10 ;");
                }
                //[... More code....]

            //[... More code....]

//[... More code....]

So basically the method is too big but at the same time I am having trouble figuring out how should I break it down. It is a method that takes an URI, breaks it down, and builds a query, so even if I split it into smaller methods, I fail to see how to refactor so the logic applies more often and I can reduce the total number of lines.

  • What is your objection to using Hibernate (or some other ORM)? It will be considerably less code, and more maintainable, than writing out all those SQL queries manually. – Sean Burton May 21 '18 at 15:17
2

I do not know how could I make the code more Object Oriented and properly refactor it

You don't want your code to be "more Object Oriented". OO is not an end in itself, it is a means to an end. You want to write less code, especially less boilerplate code, less repeated code. I guess lots of your API implementation contains very similar looking sections you don't want to repeat over and over again.

Unfortunately, there is no silver bullet to this. You need to apply several design techniques to keep the code evolvable, some of them maybe OO techniques, some of them more functional, some of them more general like the SOLID principles. As a starter, I would like to mention some tactics which, when done rigidly, will help you to get you nearer to that goal:

  • refactor heavily to smaller methods: 200 lines is far to big, even 20 is IMHO still too big. If you want your code become more DRY, you need a set of basic reusable functions I would expect not to contain more than 2 to 6 lines per method. I recommend you try to develop the following habit: whenever you use "copy-paste" in your editor, hold your breath for a moment and ask yourself if you can refactor at least parts of what you are copying into a separate method.

  • try to make your implementation more data driven: develop a neutral, concise, machine readable description of your API parameters and your data model, put that in a tabular data structure or some XML file, and try to generate alle the related SQL statements from it. This will need access to some meta data (like column names and data types in of the db tables), you can either provide these in the description table, or retrieve this from the data base. And an extension of this idea:

  • develop your own "code generator": if generating the SQL statements is not enough, why not generate most of the CRUD code by yourself? For one or two db tables this would probably be not worth it, but when the number of tables exceeds 10, this will typically pay off.

Generator tools can be implemented in various ways, using some text or XML files as input, meta data from a database, class annotations and reflection, or even some domain specific language. The generation can be done before compile time, or at run time, or both. There is no "one-size-fits-all" solution to this, you need to experiment and find out what works best for your situation.

Now you might say "my SQLs look all different, each of them has these different GROUP BY or LIMIT statements, they are not uniform enough", but this can be approached, for example, by introducing some views on your database encapsulating the joins, aggregates and limits, so at least the views can be accessed in a uniform manner. For cases which cannot be solved that easily, you may be forced to write the code manually. But even if it turns out you need to write 30% of your CRUD code without your generator tools, then it is still an advantage over your current situation, since you saved 70% of the "boring code".

  • 1
    I did't comment anything yesterday but I've read the answer several times and intend on implementing as much as I can, this is exactly what I needed I just have to find a way to assimilate the lesson! Thanks for the help. – monkey intern May 22 '18 at 8:12
2

You're looking for something like a dispatcher, i.e. something that maps a path/URI to some action/controller, e.g.

/API/profile/:id => ProfilesController.getProfileById()
/API/skill/:id   => SkillsController.getSkillById()

This is, given the path on the left, call the class/method on the right.

This way you can split all the different actions in several small classes which would be more maintainable.

Keep in mind that the URIs might have parameters so, there has to be some way to pass those from the router to the controller. Also, depending on the requirements, there can be parameters in the path, the query string, headers, etc. So, you could extract all those parameters into a Request class. And then your controllers would dispatch that request.

Roughly speaking, it could be like this:

  1. Receive an URI, parse it into a Request object
  2. Select the appropiate controller given the Request
  3. Call a method on the controller

You can of course use your own home-made router/dispatcher, or... you can use a framework like Spring which (among other things) does exactly what I mentioned.

If you're really interested in implementing your own, what I described is pretty much the Strategy pattern.

  • This is of great help. Innitialy I discarded Spring because we are working serverless and I was afraid it would add too much over-head but now the complexity of the API is growing so I feel it is soon going to be more worth it than keep reinventing the wheel. I really appreciate the time taken to answer, I have trouble addapting existing patterns into my code solutions. – monkey intern May 22 '18 at 8:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.