Skip to main content

Make Our Code More Testable with Proxy Design Pattern


If you heard about the term separation of concerns, you could agree this concept is very important for making a system "clean". One of the most important characteristics of a clean system is testable.

Let me tell you my story about how I've just come acrosss the design pattern Proxy.

Note: to get started, you can find a very simple example of the pattern Proxy here

Let's start!

I have an interface as below:
public interface DocumentGenerator {
    File generate(Document document) throws BusinessException;
}

The following is my first implementation for a concrete class of DocumentGenerator.
public class DocumentGeneratorImpl implements DocumentGenerator {
 private Dossier dossier;
 private Locale locale;
 
 public DocumentGeneratorImpl(Dossier dossier, Locale locale) {
  validateNotNullParams(dossier, locale);
  this.dossier = dossier;
  this.locale = locale;
 }
 
 private void validateNotNullParams(LibertyDossier dossier, Locale locale) {
  if(Objects.isNull(dossier)) {
   throw new IllegalArgumentException("Dossier must not be null!");
  }
  
  if(Objects.isNull(dossier.getDossierType())){
   throw new IllegalArgumentException("Dossier type must not be null!");
  }
  
  if(Objects.isNull(locale)) {
   throw new IllegalArgumentException("Locale must be defined!");
  }
 }
 
 @Override
 public File generate(Document document, boolean temporary) 
  throws BusinessException {
  setCobIdForDossierIfNotExist();
  switch (dossier.getDossierType()) {
  case TYPE_A:
   // Do some logic here in case TYPE_A
  case TYPE_BA:
   // Do some logic here in case TYPE_B
  default:
   throw new BusinessException("Not supported dossier type");
  }
 }
 
 private void setCobIdForDossierIfNotExist() {
  if(StringUtils.isEmpty(dossier.getCobId())) {
   String generatedCobId = DossierService.getInstance().generateCobId();
   dossier.setCobId(generatedCobId);
  }
 }
}

The client code constructs the concrete class DocumentGeneratorImpl.
DocumentGenerator  generator = new DocumentGeneratorImpl(dossier, locale);

I saw that some private methods "validateNotNullParams(dossier, locale)" and "setCobIdForDossierIfNotExist()" are just a second responsibility of class DocumentGeneratorImpl.

Follow "S" of SOLID  - Single Responsibility

Firstly, I was thinking about how to separate these methods into another class. Then, I create a class called DocumentGeneratorHelper which contains to these methods. It was just improved a bit.

public class DocumentGeneratorImpl implements DocumentGenerator {
 private Dossier dossier;
 private Locale locale;
 
 public DocumentGeneratorImpl(Dossier dossier, Locale locale) {
  DocumentGeneratorHelper.validateNotNullParams(dossier, locale);
  this.dossier = dossier;
  this.locale = locale;
 }
 
 @Override
 public File generate(Document document, boolean temporary) 
   throws BusinessException {
  DocumentGeneratorHelper.setCobIdForDossierIfNotExist();
  switch (dossier.getDossierType()) {
  case TYPE_A:
   // Do some logic here in case TYPE_A
  case TYPE_BA:
   // Do some logic here in case TYPE_B
  default:
   throw new BusinessException("Not supported dossier type");
  }
 }
 
}

However, if I create tests for DocumentGeneratorImpl, I need to mock DocumentGeneratorHelper. Huh....! Any better approach?

I was thinking about that it is somehow I need to apply an Aspect-Oriented Programming (AOP) approach. As my google searching result, there are some approaches but they are quite complicated and heavy. They use Java Proxy or AOP Frameworks such as AspectJ.

A Simple AOP Approach

Fortunately, the keyword "proxy" leads me to the pattern Proxy. The idea of Proxy is really simple and cool!



Okay, now I create a Proxy instead of a Helper as before.

public class DocumentGeneratorProxy implements DocumentGenerator{
 private DocumentGenerator generator;
 private Dossier dossier;
 
 public DocumentGeneratorProxy(Dossier dossier, Locale locale) {
  validateNotNullParams(dossier, locale);
  this.dossier = dossier;
  this.generator = new DocumentGenerator(this.dossier, locale);
 }

 private void validateNotNullParams(Dossier dossier, Locale locale) {
  if(Objects.isNull(dossier)) {
   throw new IllegalArgumentException("Dossier must not be null!");
  }
  
  if(Objects.isNull(dossier.getDossierType())){
   throw new IllegalArgumentException("Dossier type must not be null!");
  }
  
  if(Objects.isNull(locale)) {
   throw new IllegalArgumentException("Locale must be defined!");
  }
 }

 @Override
 public File generate(Document document, boolean temporary)
   throws BusinessException {
  setCobIdForDossierIfNotExist();
  return generator.generate(document, temporary);
 }

 private void setCobIdForDossierIfNotExist() {
  if(StringUtils.isEmpty(dossier.getCobId())) {
   String generatedCobId = DossierService.getInstance().generateCobId();
   dossier.setCobId(generatedCobId);
  }
 }

}

Done! DocumentGeneratorImpl  doesn't contain its second responsibilities anymore. These methods are tested by the Proxy and we don't need to mock in DocumentGeneratorImpl.
the public class DocumentGeneratorImpl implements DocumentGenerator {
 private Dossier dossier;
 private Locale locale;
 
 public LibertyDocumentGenerator(Dossier dossier, Locale locale) {
  this.dossier = dossier;
  this.locale = locale;
 }
 
 @Override
 public File generate(Document document, boolean temporary) 
   throws BusinessException {
  switch (dossier.getDossierType()) {
  case TYPE_A:
   // Do some logic here in case TYPE_A
  case TYPE_BA:
   // Do some logic here in case TYPE_B
  default:
   throw new BusinessException("Not supported dossier type");
  }
 }
 
}

So, instead of calling directly DocumentGeneratorImpl, we call DocumentGeneratorProxy in the client code as below:

DocumentGenerator  generator = new DocumentGeneratorProxy(dossier, locale);

Happy codings!

Comments

Popular posts from this blog

[Snippet] CSS - Child element overlap parent

I searched from somewhere and found that a lot of people says a basic concept for implementing this feature looks like below: HTML code: <div id="parent">  <div id="child">  </div> </div> And, CSS: #parent{   position: relative;   overflow:hidden; } #child{   position: absolute;   top: -1;   right: -1px; } However, I had a lot of grand-parents in my case and the above code didn't work. Therefore, I needed an alternative. I presumed that my app uses Boostrap and AngularJs, maybe some CSS from them affects mine. I didn't know exactly the problem, but I believed when all CSS is loaded into my browser, I could completely handle it. www.tom-collinson.com I tried to create an example to investigated this problem by Fiddle . Accidentally, I just changed: position: parent; to position: static; for one of parents -> the problem is solved. Look at my code: <div class="modal-body dn-placeholder-parent-positi...

What the heck is Meteor DDP?

I was using Meteor for my messenger project. I was so curious about the real time connection. I wanted to know how exactly this mechanism works. In this post, I will go through the DDP Specification, an overview of WebSocket, and a simple demo about how to subscribe a publication of Rocket.Chat (containing a DDP server) from an external webpage. At a glance, I knew that Meteor invented a protocol called DDP which uses for handling real time connection. So then, what is DDP? "DDP (Distributed Data Protocol) is the stateful WebSocket protocol that Meteor uses to communicate between the client and the server." [1] All right! Why does DDP matter? "DDP is a standard way to solve the biggest problem facing client-side JavaScript developers: querying a server-side database, sending the results down to the client, and then pushing changes to the client whenever anything changes in the database" . [2] In order to understand deeply the protocol, I decided ...

Why Business Rules Engine Matter

"A good DSL minimizes the 'communication gap' between a domain concept and the code that implements it" - Robert C. Martin A Use Case It looks like adopting a new technology is always driven by a business need. The organizations, such as banks, have their own business processes (such as data gathering and document management). These processes are different from others but usually the differences are not big. How do we build a product which can be reused the similar features but still be adaptable with the specific requirements of each bank? Then, the answer is we should have a library/framework. There is an idea that we can implement this library/framework by using a business rules engine. Which has the following benefits: Be able to modify implementation of business domain at runtime (such as XML, CSV) S ource code is more readable for both developers and domain experts. Therefore, source code can be consider it as a document Since Business Rules Engine...

MS SQL Server Views

"Creates a virtual table whose contents (columns and rows) are defined by a query. Use this statement to create a view of the data in one or more tables in the database. For example, a view can be used for the following purposes: - To focus, simplify, and customize the perception each user has of the database. - As a security mechanism by allowing users to access data through the view, without granting the users permissions to directly access the underlying base tables. - To provide a backward compatible interface to emulate a table whose schema has changed." [1] Beside that, our team used view in order to improve the performance of our web apps when a database has a very complicated relationship between its tables by using ORM Frameworks such as Hibernate. Example code: --create CREATE VIEW placeholders AS SELECT EMPKEY AS empkey, CONNUMB AS connumb, EMPNBR AS empNbr, ACEEMPN AS empFirstName, ACEEMPFN AS empLastName, EMPNAM AS empFullName, ...

Why Functional Programming Matter

What issues do we concern when implementing and maintaining systems? One of the most concern is debugging during maintenance: "this code crashed because it observed some unexpected value." Then, it turns out that the ideas of  no side effects  and  immutability , which functional programming promotes, can help. Shared mutable data is the root cause Shared mutable data are read and updated by more than one of the methods. Share mutable data structures make it harder to track changes in different parts of your program. An immutable object is an object that can't change its state after it's instantiated so it can't be affected by the actions of a function. It would be a dream to maintain because we wouldn't have any bad surprises about some object somewhere that unexpectedly modifies a data structure. A new thinking: Declarative programming There are two ways thinking about implementing a system by writing a program. - Imperative programming: has...