Lesson Weekend

Today is an exciting day of Java! It’s time to explore the first of several approaches to solving complex problems in programming generally dubbed Software Design Patterns.

What are Software Design Patterns?

Software Design Patterns are commonplace, longstanding ways to design and develop complex applications. They are usually language-agnostic, meaning they can be found in different languages, although usually implemented slightly differently.

Software Design Patterns are usually implemented to address issues of scale: While it may be easy to make a small application that does one or two things really well, when the application has to scale and change - either with regard to the amount of things it must do, or the environment in which it runs. If we don’t think ahead, we might get stuck with an expensive re-write that can even push a startup out of business.

Software Design Patterns are approaches that seek to minimize complications and code rewrites when applications begin to scale and change. They are important theoretical approaches that can make the difference between a successful app (-> product, -> company) and an unsuccessful one. Let’s jump right in.

We’re going to explore the Software Design Pattern of creating and using a Data Access Object today.

Meet Teresa

Teresa is excited about her new hobby - tennis. Teresa is five years old. So far, she’s only really had a chance to bat a ball against her garage door with papa’s racket. But she knows she really gets a kick out of hitting the ball hard and returning it when the door hits it back. And she gets it every time!

Theresa is just a kid, and doesn’t know much about the sport of tennis. She only knows batting a ball against a garage door. If Teresa-as-tennis-player were an application, she would likely have a few tennis-related methods, and a few loops - she doesn’t need much more at this point.

But Teresa’s Dad quickly realizes that Teresa not only has a passion for the game, but potentially the talent to become pro. After thinking things through, and spending a lot of time talking to Teresa about it, Teresa’s Dad decides to help Teresa level up in tennis.

He now has a big decision to make: Does he coach her himself? Does he take her to the local community tennis court, and have her play against the ball machine? Or does he enroll her in expensive tennis camps where she will play against a variety of opponents, and work with a variety of coaches? Money is tight for Teresa’s Dad. He’s working for a startup building a promising new app prototype, but he doesn’t know when how they’ll make to market. If he chooses the wrong way, he might lose money he doesn’t really have - tennis camps are expensive, and it’ll be time consuming and demanding for her to follow a strict regimen. But the local court and the ball machine won’t do much in terms of making her a well-rounded player. What should he do?

Data Access Objects

Let’s switch gears and consider the following POJO:

Match.java
public class Match {

   private int id;
   private String name;
   private String location;

   public Match(String name, String location) {
       this.name = name;
       this.location = location;
   }

   public int getId() {
       return id;
   }

   public void setId(int id) {
       this.id = id;
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public String getlocation() {
       return location;
   }

   public void setlocation(String location) {
       this.location = location;
   }
}

The code above is a very basic class definition. It simply describes how the class is shaped and what minimum methods it contains. If we wanted to receive information from a database, say a Postgres database, we would need code to connect with a database and retrieve information that we could then turn into a Match object.

We could write the methods into our Match.java file, like so:

Match.java
public class Match {

   private int id;
   private String name;
   private String location;

   public Match(String name, String location) {
       this.name = name;
       this.location = location;
   }

  ...

   public String getlocation() {
       return location;
   }

   public void setlocation(String location) {
       this.location = location;
   }
}

@Override
public void add(Match match) {
   String sql = "INSERT INTO matches (name, location) VALUES (:name, :location)";
   try(Connection con = sql2o.open()){
       int id = (int) con.createQuery(sql, true)
               .bind(match) //use name and location from match object for sql
               .executeUpdate()
               .getKey();
       match.setId(id);
   } catch (Sql2oException ex) {
       System.out.println(“there was a problem adding the match);
   }
}

@Override
public List<Match> findAll() {
   try(Connection con = sql2o.open()){
       return con.createQuery("SELECT * FROM matches")
               .executeAndFetch(Match.class);
   }
}

(We will be writing code very similar to this a little later in this unit, so don’t worry if you don’t understand everything here yet.) But as you can probably guess, the first method adds a Match to the matches database, and the second method finds all objects of the Match type. Great. We can now connect to a Postgres database and work with Match data.

And this will totally, totally work. It’s simple, it’s straightforward, and it works.

But it’s bad practice and you shouldn’t do it. Why? Well, what if the app grows from a simple match tracking app for a local club to the go-to app for tracking all tennis players, matches, rankings, scores, tournaments, locations, etc. worldwide? It would be a massive application. Potentially containing hundreds and hundreds of class files.

And then, what if we switched from Postgres to an Oracle database, because the app’s uptime has been suffering due to it’s giant dataload running off a dinky postgres server, and we need something that can handle Enterprise level volume. We would have to rewrite every single DB access method in every single class definition. This would be a huge problem, and take a giant chunk of time and effort - weeks and weeks potentially. This app does not scale well as-is. What to do?

Teresa Learns More About Tennis and Life

Teresa’s dad has finally made a decision: Money is just too tight, so he opts to coach Teresa himself. He spends his weekends and evenings with her, playing at the local tennis courts, and teaching her all he remembers from his own childhood. Teresa also practices with the ball-launching machine - she gets really good at returning the balls regardless of which angle they come from.

Teresa-as-an-app has gained functionality. She can now play basic tennis, and has learned from the ball launcher how to return balls arriving from a variety of angles. She also learns to win against her dad, which bodes well.

Teresa has her debut at a local tennis event. Despite her hard work and obvious talent, she loses. She is completely under-prepared to play against someone besides her dad or the ball launching machine. After the match, Teresa realizes she will have to make a LOT of changes and grow as player quickly in order to compete. She worries whether she is already too set in her ways.

It sounds harsh, but Teresa has failed to scale effectively. If Teresa had learned to play against a variety of different environments, weather conditions, surfaces, players, times of day, rackets, etc., she would have been able to learn to adapt and succeed. She needs to separate herself from her opponent (Dad/ball launcher) and become a player that can compete regardless of context.

Making the Connection to Our Code: Data Access Objects

So now that we understand why we must separate code that connects to the database from code that creates our objects, how and where do we implement that functionality?

What we need is a DAO, or Data Access Object. Back to our huge tennis tracking app. If we were tasked with changing our entire giant app to work on Oracle instead of Postgres, we would have two options:

  • Rewrite the entire application to use Oracle (or add conditional code to handle the differences), or
  • Create a layer between your application logic and the data access

A DAO allows us to do the latter. DAOs act as the intermediary between the application and the database, and remove the need for each piece of our application to know exactly how the other part of our application works. It separates the two parts of the application that are not supposed to know anything about each other. It also allows for an additional benefit: unit testing mock data without the database in place. It allows us to build applications that work independently of which kind of database server they are deployed to.

If this still feels confusing, consider Teresa: She represents our application. The application contains logic; for this example, logic on how to return a ball that is speeding towards her in the strategically best way.

In order to refine her play (internal logic), Teresa has to continuously refactor and practice her abilities to achieve a greater skill level. As we know, Teresa only practiced with her dad and the ball machine. She learned how to play in a specialized way, but failed when she needed to play another human. She needs to learn to play more generically. The DAO here is represented by an agreement that the player makes to handle all shots played at them the same, i.e, generically, regardless of whether they are served by a machine or human opponent. Through this agreement, the object that serves the data (machine or player) becomes irrelevant.

How We Implement Our DAO

Think back to our lesson on interfaces. As we discussed, interfaces are a contract we can sign that guarantees that our classes will implement a basic set of methods. They are rules and guidelines that remind us to make sure we are creating consistent code. They don’t contain code per se, but if we forget to implement methods we need, the interface will warn us that something is missing.

Let’s create an interface DAO that contains information about which methods we’ll have to implement:

MatchDao
public interface MatchDao {
   void add (Match match);

   List<Match> findAll();

}

The interface literally states: "Make sure you implement these two methods: add() and findAll()." Cool. Now we can create our logic to connect to our database server, and move the code that we wrote in our class definition into this file:

PostgresMatchDao
public class PostgresMatchDao implements MatchDao{

...

@Override
public void add(Match match) {
   String sql = "INSERT INTO matches (name, location) VALUES (:name, :location)";
   try(Connection con = sql2o.open()){
       int id = (int) con.createQuery(sql, true)
               .bind(match) //use name and location from match object for sql
               .executeUpdate()
               .getKey();
       match.setId(id);
   } catch (Sql2oException ex) {
       System.out.println(“there was a problem adding the match);
   }
}

@Override
public List<Match> findAll() {
   try(Connection con = sql2o.open()){
       return con.createQuery("SELECT * FROM matches")
               .executeAndFetch(Match.class);
   }
}

Great. Now our data access code is totally separate from our data model. If we need to switch our DB implementation from Postgres to Oracle, all we would need to do is to change this file, instead of needing to make changes in the data model directly. Our model keeps booping along like nothing changed.

This abstraction is, conceptually, mirrored by:

  • Separation of backend and frontend
  • Separation of design (css) and document structure (html)

We want to make our apps scalable by detaching different parts so that they can be easily updated, changed or expanded. We’ll explore how to both call the add() and findAll() methods, and how to integrate our DAO object with Spark later on.

And Theresa? Her dad took a long walk and decided to quit his Steam membership, and sell some of his vintage star wars memorabilia. She’ll be enrolling in a tennis camp this summer and is well on her way to a successful career.