Plugins in concept and ways to implement it (exercise in Java)
Objective of this post
The word "plugins" in computer software is about the extension or customization of an application. In this post I want to show you different implementation (or better called thinking) ways of plugins in JAVA with some important code snippets to take away rather than a step-by-step how to implement the plugins (because the parts about Microservices and Cloud service requires many setups behind. You will have the code with you for a full demo). In case you have any question don't hesitate to post it below. Your question may help others
Scenario of the demo application
For better demonstration I will implement the ways mentioned later under the same scenario described below. For each way, the scenario may be changed a little bit but the main idea is unchanged.
There is a game names Happy Farm. This is a game for children to identify animals. We need to implement a welcome screen in the game where a random animal will appear and introduce itself to the player. Since the frontend was developed by another team we only have to care about the backend. For backend we need to implement a method to show a welcome message with the following signature:
String executeCommand(String command);
The implementation should ensure that it's easy to add a new type of animal into application. Currently we only support three types: dog, cat and mouse. Below table is the output of welcome message for each animal corresponding to the input command.
Input command param
Output
DOG~White FangGau gau, White Fang, your loyal servant is here. Nice to meet you, my boss.CAT~TomMeo meo, Tom is here. Do you want to caress me?MOUSE~JerryChit chit, I'm Jerry. Remember to protect your carefully, kaka.
Here are some notes for the imeplementation:
Solutions
Method solution
Implementation
Switch case or multiple if clauses is the easiest and quickest level to implement . Maybe some of you can think of something like this (in the code below Animal is just a DTO with 2 fields name and type. AnimalTypeEnum has 4 fields: UNIDENTIFIED, DOG, CAT and MOUSE)
Sample Method level implementation - OriginalMethodSolutionImpl.java - Expand source
However, everything has a tradeoff. Back to the scenario above you can see that:
So, if we only have a little time to do and a new animal won't come soon into application, what can we do? Let's have a look at the code below
Lamda approach for method solution - LambdaMethodSolutionImpl.java Expand source
Summary
So, let's compare with the points of the implementation above:
For a comparison with pros and limitations of this solution, please check the chapter Usage summary for a full picture. Now let's see if the class solution can solve these challenges or not.
Class solution
Introduction
First, the spec is unchanged. We keep the same requirements as in the Scenario. The objective of this solution is to solve the issues in the "should be avoided" cases in method level above. The solution I mentioned below bases on the support from Spring. Since at ELCA we use Spring as our main framework in work I use it to demonstrate my idea. (I don't know the other frameworks can have something similar or not). Thanks to Spring the combination between command pattern and factory pattern can bring a super easy solution for us.
Implementation
The idea here is that I will have a list of parser and each parser support two main methods: "boolean canParse(String testCommand);" and "String executeCommand(String command);". What the main "executeCommand" method does is to loop through the parsers list to see if there is any parser support the input command or not. The first one found will be used to parse the command. Tada, you can see know that the logic inside the "executeCommand" method will become easy and unchanged (even if you change the logic of welcome message). You will have something like this
Here I take advantage of the bean discovery mechanism of Spring to automatically detect the parser beans. If your project doesn't use Spring you can find the supported IoC from the framework being used or you can implement yourself. This IoC mechanism is the key point for this solution.
Sample solution with using Spring bean autowire mechanism - ClassSolutionImpl.java Expand source
And here is a class diagram of the parser
Explanation
For the detail of the classes you can check in the attached source code below. Here are some useful code snippet so that you can quickly understand the solution (The Animal and AnimalType is reused here)
CommandParser.java Expand source
AbstractCommandParser.java Expand source
CatCommandParserImpl.java Expand source
Summary
As you can see this approach solves the limitations of the method level: Each animal type is in a separated class
Recommended by LinkedIn
Challenges
So, let's see if Module level solution can help you with these limitations.
Module solution
Introduction
First, the spec is unchanged. We keep the same requirements as in the Scenario. With this solution we move the animal type parser classes into JAR modules and add dependencies from the current module to those JAR modules. That's the implementation idea of Module level. So, there is no implementation demo here because the different between this and the class level is only modularization of the application 😎.
Summary
This solution includes all the Pros from class level because they are only different from code organization. Beside, it also has some more advantages
However, this solution still can't solve the challenges of class level. (For availability, yeah, we have something like hot reload but it's not for production env, only test env can take advantage of this. A restart of the application instance still takes times no matter how fast it is). So, let's see if microservice level can solves the remained challenges of Class and Module solutions.
Microservices solution
Introduction
Due to the limitation of the source code (I don't want to let you setup docker-compose for a whole microservices system because it will raises tons of configuration questions) I will change the spec a bit. Now there is no more executeCommand() method to implement. There is just one main() method and with the inputs as defined in the Scenario you need to output the same as you did with class solution and method solution.
Now, about microservice level. The objective of this solution is to solve the two hot points which class solution and module solution can't
Implementation
The picture below shows how microservices solution is implemented for this scenari
o
The main entry point I suggest you to go with the source code for debugging is "MicroserviceSolutionDemoApplication".
Click here to view how to run/debug the application in the source code chapter...
Summary
Challenges
Cloud solution
Implementation
The picture above shows how the solution will be implemented: we will have 4 components:
In the source code chapter below you can find the demo application. Below are the steps to run it.
Click here to expand...
Summary
As you can see. now because it's REST service and you can add load balancing, running in multiple nodes for a single service to make sure that it can perform with most productivity as you want. Things seems to be good? 😜 There is no really good thing. Same in this case. Here the points which affect your application will be
Usage summary
From the pros and challenges of the solutions above I summarize to the table below. I give you a way to think because it's mostly my personal opinion, please use it in a flexible way
Final words
These are the ways I know and applied in the project. There may be more ways (techniques is changing day by day ). As usual, don't limit your thought, study new supports from the languages and frameworks you can make your code better and your life easier
Source code
Please find in the attached file HappyFarm-Plugins.zip for the source code. This source code is just for demonstration purpose. The skeleton and setup inside should be used for reference only.