...
 
Commits (1)
Poor Example
My objective in preparing Poor Example was to explore beyond a narrow ability to remember facts. It is an exploration of the understanding Java developers have of good design principles and their natural inclination towards quality. This test attempts to identify developers who care about the quality of their work, beyond simply the functionality and into the extensibility and simplicity of their code.
Poor Example is a very simple application that contains some common design faults or limitations. The test instructions do not specify that the developer should do anything but add new functionality. The time constraint for the exercise is generous, which gives developers a choice between completing only what was asked for and no more, or taking a more proactive approach in fixing the design limitations and taking longer. Developers who care about quality will have the courage and pride in their work to do more than that requested. The issues should be reasonably self evident to any developer adding new functionality.
I have provided two projects. The first is the project to be given to the test subject. This should be sent to the test subject along with the instructions. The second project is an example solution. Note that this is not a pass/fail kind of test, in that we are not looking for a well defined solution, but rather one that recognises and addresses the design limitation. It informs us whether developers have a real and deeper appreciation of design principles and a commitment to quality over speed.
Instructions: ------------
Vladimir Antanov has fled the country with his exotic girlfriend Sonya. My company employed Antanov to maintain our critical communication systems. We urgently need to add a new feature the the existing software, and knowing you are a Master Java Developer we have chosen you to modify the software to communicate with our new clients.
Please find attached the project and source code. Currently the system processes notifications and sends them to email, database or a web service. We need to add another endpoint - JMS.
The JMS connection requires configuration details:
- ServerName
- Port
- QueueName
You have one day to add the JMS functionality to the existing code and return it by email to me.
Note: We don't expect an actual JMS connector - follow the same functionality as the existing senders which simply output to the console.
Solution: -----------------
Basic Requirements
Before getting into the detail of the solution offered check the following:
* That the code compiles and runs.
* That it produces the right output.
Alert.java
This is the Alert Bean which specifies the services to call when triggered. The original Alert class includes booleans representing each kind of service.
Principle #1: Closed to Change, Open to Extension.
In order to add the new JMS service a new boolean would need to be added to the Alert class if the existing approach was followed. But ideally the Alert class should not need to be changed just because a new kind of service is introduced. In order to close Alert to modification we need to decouple this class from the services available. The solution Alert class therefore introduces a list of Senders, where Sender is an interface that will be implemented by the sending Services. All the boolean values are therefore no longer required, replaced with a list of Senders.
* Did the subject modify Alert to include a new boolean to handle the new service, or did they modify it so that it was no longer dependent on the detail of services available?
MainApplication.java
Principle #2: Dependency Injection
The original MainApplication creates the instances of the service classes it will use by itself. The whole point of using Spring and other dependency injection frameworks is that you can inject new implementations easily. This is very useful for testing, where you can isolate classes to test. By ignoring this principle and creating the classes internally there is no way to test the class in isolation.
* Did the subject identify the problem of dependency injection and remove the creation of the services from within MainApplication?
Principle #3: Separation of Concerns
The original MainApplication stores the configuration details of the services inside itself. It then uses various different mechanisms to transmit the configuration downstream to the various services. If the original approach is followed the new configuration information for JMS would need to be added to MainApplication. However, the configuration details of each service is not the concern of the MainApplication, and should be moved out to the service classes themselves. Also, there is a block of if statements which call different services in different ways. If the subject follows this parrern they will need to add an if block. But by introducing a Sender interface and a single standard method call they can all be merged into a single call.
* Did the subject identify that configuration is not the concern of MainApplication and move the configuration to the service classes?
* Did the subject identify that the 'if statements' for each service type can be removed with the help of an Interface?
* Did the subject remove dependencies in MainApplication on the service implementations?
DatabaseSender.java, EmailSender.java, WebServiceSender.java
Principle #4: Remove Circular Dependencies
The original examples had a classic circular dependency between the MainApplication class and the service classes DatabaseSender, EmailSender and WebServiceSender. Each of these sender classes uses a different mechanism to inject the configuration. Instead the configuration information should be moved into these sender classes.
* Did the subject remove the dependency on MainApplication?
* Did the subject move the configuration fields into the sender classes?
package org.devcentre.poorexample;
import java.util.List;
import org.devcentre.poorexample.sender.Sender;
public class Alert {
private String code;
private String message;
private boolean sendToEmail;
private boolean sendToDatabase;
private boolean sendToWebService;
private List<Sender> senders;
public void trigger(){
for( Sender sender : senders){
sender.notify(this);
}
}
public String getCode() {
return code;
}
......@@ -23,29 +32,13 @@ public class Alert {
public void setMessage(String message) {
this.message = message;
}
public boolean isSendToEmail() {
return sendToEmail;
}
public void setSendToEmail(boolean sendToEmail) {
this.sendToEmail = sendToEmail;
}
public boolean isSendToDatabase() {
return sendToDatabase;
}
public void setSendToDatabase(boolean sendToDatabase) {
this.sendToDatabase = sendToDatabase;
public void setSenders(List<Sender> senders) {
this.senders = senders;
}
public boolean isSendToWebService() {
return sendToWebService;
public List<Sender> getSenders() {
return senders;
}
public void setSendToWebService(boolean sendToWebService) {
this.sendToWebService = sendToWebService;
}
}
......@@ -2,46 +2,15 @@ package org.devcentre.poorexample;
import java.util.List;
import org.devcentre.poorexample.sender.DatabaseSender;
import org.devcentre.poorexample.sender.EmailSender;
import org.devcentre.poorexample.sender.WebServiceSender;
public class MainApplication {
public static String emailServer;
public static String emailPort;
public static String databaseServer;
public static String databaseUser;
public static String databasePassword;
public static String webServiceUrl;
public static String webServiceService;
private List<Alert> alerts;
private List<String> notifications;
private EmailSender emailSender = new EmailSender();
private DatabaseSender databaseSender = new DatabaseSender(this);
private WebServiceSender webServiceSender = new WebServiceSender();
public void run() {
public void run() {
for( String notification : notifications ){
Alert alert = findAlert( notification );
if( alert.isSendToDatabase()){
this.databaseSender.submit( alert );
}
if( alert.isSendToEmail()){
this.emailSender.send( alert );
}
if( alert.isSendToWebService()){
this.webServiceSender.transmit( alert, webServiceUrl, webServiceService );
}
alert.trigger();
}
}
......@@ -71,43 +40,4 @@ public class MainApplication {
return notifications;
}
public void setEmailServer(String server) {
emailServer = server;
}
public void setEmailPort(String port) {
emailPort = port;
}
public void setDatabaseServer(String server) {
databaseServer = server;
}
public void setDatabaseUser(String user) {
databaseUser = user;
}
public void setDatabasePassword(String password) {
databasePassword = password;
}
public void setWebServiceUrl(String url) {
webServiceUrl = url;
}
public void setWebServiceService(String service) {
webServiceService = service;
}
public void setEmailSender(EmailSender sender) {
emailSender = sender;
}
public void setDatabaseSender(DatabaseSender sender) {
databaseSender = sender;
}
public void setWebServiceSender(WebServiceSender sender) {
webServiceSender = sender;
}
}
package org.devcentre.poorexample.sender;
import org.devcentre.poorexample.Alert;
import org.devcentre.poorexample.MainApplication;
public class DatabaseSender extends AbstractSender{
public class DatabaseSender extends AbstractSender implements Sender{
private MainApplication app;
private String server;
private String user;
private String password;
public DatabaseSender( MainApplication app ){
this.app = app;
}
public void submit( Alert alert ){
public void notify( Alert alert ){
display( "Database", alert);
System.out.println("Server: " + this.app.databaseServer);
System.out.println("User: " + this.app.databaseUser);
System.out.println("Password: " + this.app.databasePassword);
System.out.println("Server: " + server);
System.out.println("User: " + user);
System.out.println("Password: " + password);
}
public void setServer(String server) {
this.server = server;
}
public String getServer() {
return server;
}
public void setUser(String user) {
this.user = user;
}
public String getUser() {
return user;
}
public void setPassword(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
}
package org.devcentre.poorexample.sender;
import org.devcentre.poorexample.Alert;
import org.devcentre.poorexample.MainApplication;
public class EmailSender extends AbstractSender{
public class EmailSender extends AbstractSender implements Sender{
public void send( Alert alert ){
private String server;
private String port;
public void notify( Alert alert ){
display( "Email", alert);
System.out.println("Host: " + MainApplication.emailServer);
System.out.println("Port: " + MainApplication.emailPort);
System.out.println("Host: " + server);
System.out.println("Port: " + port);
}
public void setServer(String server) {
this.server = server;
}
public String getServer() {
return server;
}
public void setPort(String port) {
this.port = port;
}
public String getPort() {
return port;
}
}
package org.devcentre.poorexample.sender;
import org.devcentre.poorexample.Alert;
public class JmsSender extends DatabaseSender implements Sender {
private String server;
private String port;
private String queueName;
public void notify( Alert alert ){
display( "JMS", alert);
System.out.println("Host: " + server);
System.out.println("Port: " + port);
System.out.println("QueueName: " + queueName);
}
public void setServer(String server) {
this.server = server;
}
public String getServer() {
return server;
}
public void setPort(String port) {
this.port = port;
}
public String getPort() {
return port;
}
public void setQueueName(String queueName) {
this.queueName = queueName;
}
public String getQueueName() {
return queueName;
}
}
package org.devcentre.poorexample.sender;
import org.devcentre.poorexample.Alert;
public interface Sender {
void notify( Alert alert );
}
......@@ -2,12 +2,31 @@ package org.devcentre.poorexample.sender;
import org.devcentre.poorexample.Alert;
public class WebServiceSender {
public class WebServiceSender extends AbstractSender implements Sender{
public void transmit( Alert alert, String url, String service ){
System.out.println("\nSending Web Service Alert: " + alert.getMessage());
private String url;
private String service;
public void notify( Alert alert ){
display( "WebService", alert);
System.out.println("URL: " + url);
System.out.println("Service: " + service);
}
public void setUrl(String url) {
this.url = url;
}
public String getUrl() {
return url;
}
public void setService(String service) {
this.service = service;
}
public String getService() {
return service;
}
}
......@@ -3,41 +3,79 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="emailSender" class="org.devcentre.poorexample.sender.EmailSender">
<property name="server" value="devcentre.org"/>
<property name="port" value="25"/>
</bean>
<bean id="databaseSender" class="org.devcentre.poorexample.sender.DatabaseSender">
<property name="server" value="localhost"/>
<property name="user" value="admin"/>
<property name="password" value="password"/>
</bean>
<bean id="webServiceSender" class="org.devcentre.poorexample.sender.WebServiceSender">
<property name="url" value="http://devcentre.org/webservice"/>
<property name="service" value="getServices"/>
</bean>
<bean id="jmsSender" class="org.devcentre.poorexample.sender.JmsSender">
<property name="server" value="devcentre.org"/>
<property name="port" value="25"/>
<property name="queueName" value="myqueue"/>
</bean>
<bean id="mainApp" class="org.devcentre.poorexample.MainApplication">
<property name="emailServer" value="devcentre.org"/>
<property name="emailPort" value="25"/>
<property name="databaseServer" value="localhost"/>
<property name="databaseUser" value="admin"/>
<property name="databasePassword" value="password"/>
<property name="webServiceUrl" value="http://devcentre.org/webservice"/>
<property name="webServiceService" value="getServices"/>
<property name="alerts">
<list>
<bean class="org.devcentre.poorexample.Alert">
<property name="code" value="EMAIL"/>
<property name="message" value="This is the EMAIL Alert - it sends an email"/>
<property name="sendToEmail" value="true"/>
<property name="message" value="This is the EMAIL Alert - it sends an email"/>
<property name="senders">
<list>
<ref bean="emailSender"/>
</list>
</property>
</bean>
<bean class="org.devcentre.poorexample.Alert">
<property name="code" value="DATABASE"/>
<property name="message" value="This is the DATABASE Alert - it sends to Database"/>
<property name="sendToDatabase" value="true"/>
<property name="message" value="This is the DATABASE Alert - it sends to Database"/>
<property name="senders">
<list>
<ref bean="databaseSender"/>
</list>
</property>
</bean>
<bean class="org.devcentre.poorexample.Alert">
<property name="code" value="WEB"/>
<property name="message" value="This is the WEB Alert - it sends to Web Service"/>
<property name="sendToWebService" value="true"/>
<property name="senders">
<list>
<ref bean="webServiceSender"/>
</list>
</property>
</bean>
<bean class="org.devcentre.poorexample.Alert">
<property name="code" value="JMS"/>
<property name="message" value="This is the JMS Alert - it sends to JMS"/>
<property name="senders">
<list>
<ref bean="jmsSender"/>
</list>
</property>
</bean>
<bean class="org.devcentre.poorexample.Alert">
<property name="code" value="ALL"/>
<property name="message" value="This is the ALL Alert - it sends to Everything"/>
<property name="sendToEmail" value="true"/>
<property name="sendToDatabase" value="true"/>
<property name="sendToWebService" value="true"/>
<property name="senders">
<list>
<ref bean="emailSender"/>
<ref bean="databaseSender"/>
<ref bean="webServiceSender"/>
<ref bean="jmsSender"/>
</list>
</property>
</bean>
</list>
</property>
......@@ -47,6 +85,7 @@
<value>EMAIL</value>
<value>DATABASE</value>
<value>WEB</value>
<value>JMS</value>
<value>ALL</value>
</list>
</property>
......