• 4 hours
  • Hard

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 2/21/22

Extract the Model

Are you driving right now? Gosh, I hope not. 😱 But pretend you are! What things do you care about when you’re driving? Probably your speed. How much gas is in the tank. The proximity of the cars around you. Street signs. Incoming text messages.

All of these items make up the state of your current system of driving. We call all of these state-representing ideas the model. It’s all those items that are being viewed or manipulated. We want to keep all these ideas in their own entity layer (just like we separated the view layer).

Where do the model objects come from?

Remember all that time we spent figuring out what the entities are by reading through the use-case descriptions? Those are going to be the classes that belong to the model.

We’ll follow a similar set of steps for extracting the model as we did for the view.

  • Examining the existing architecture and code to see where the problems lie.

  • Defining our solutions. 

  • Applying our solutions. 

Let's get started.

Examine Issues With the Current Architecture

In our airline application, the model is being held by the Java classes we saw in the first chapter:

The Model in Our Architecture
The model in our architecture

However, those classes have some added responsibilities beyond being model classes. For example, client, reservation, pilot, and plane all create their own SQL statement objects and then run their own queries. And while model objects usually need to be persisted, it is not the job of the model classes to do it for themselves. The data storage responsibility is violating the S in the SOLID design principles. We need to fix it.

As an example, let’s take a closer look at the client class. It has data values to hold a first and last name, address, telephone number, list of reservations, and outstanding balance.

All this information is essential. If we never turned the machine off, a client object could live in memory forever. But this is an unreasonable expectation. We need to save this (and other model objects) to a persistence mechanism. Usually, that’s a database. And it looks like SQLite is what is used for the existing application.

Here is some code found in Client.java that manages its own data storage:

	private static DefaultTableModel buildTableModel() {
   	 ResultSet rs = DairyAirDb.getResultSet("SELECT * from clients");
   	 try {
   		 // details removed
   	 	while (rs.next()) {
   			 // processing this record
   	 	}
   	 	return new DefaultTableModel(data, columnNames);
   	 } catch (SQLException e) {
   		 e.printStackTrace();
   	 }
    	return null;
    }

This class again suffers from being challenging to modify, test, and maintain. If the data storage type is changed, this all has to be modified. The data storage infrastructure must be in place (or at least mocked) to test a client object. More work than should be necessary to change or test the class.

Define Our Solutions

From an architecture standpoint, we want persistence to be a stand-alone data layer. We want to separate out any direct connection from our model objects (the entity layer) to the way they are saved or retrieved from a data store (the data layer). Let's update the UML class diagram to sever the direct connection between client and SQLite:

Sever the connections in our architecture
Sever the connections in our architecture

OK, we've separated all the direct connections between our Java classes (the model classes that make up our entity layer), and the SQLite database (the data layer). But to make this work, we need to:

  • Modify the existing model classes to support being integrated into the Spring Boot framework.

  • Persist our model objects. 

Apply Our Solutions

Fortunately, Spring Boot provides a mechanism for doing this the right way. It can be carried out in just a few steps.

  • Step 1: Modify the existing Spring Boot application POM file to include a persistence layer.

  • Step 2: Copy the model classes (client, reservation, pilot, and plane) into the Spring Boot application. In this, we will:

    • Remove the calls to the SQLite database.

    • Add some annotations to the member variables of our classes. Spring Boot can recognize these annotations as something needing to be persisted.

    • Add getters and setters for each of the member variables if none exists.

  • Step 3: Add a corresponding repository class.

Let's get started.

Step 1: These are the dependencies to add to the pom.xml file:

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
    	<dependency>
        	<groupId>com.h2database</groupId>
        	<artifactId>h2</artifactId>
        	<scope>runtime</scope>
    	</dependency>

Step 2: Here’s an example of the modified client class. It goes into a new package:

com.dairyair.dairyairmvc.entities.

package com.dairyair.dairyairmvc.entities;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.NotBlank;

@Entity   // Added Data Storage Annotation
public class Client {
	@Id   // Added Data Storage Annotation
	@GeneratedValue(strategy = GenerationType.AUTO)  // Added Data Storage Annotation
	private long id;

	@NotBlank(message = "First Name is mandatory")  // Added Data Storage Annotation

	private String firstName;

	@NotBlank(message = "Last Name is mandatory")  // Added Data Storage Annotation

	private String lastName;
    
	private String address;
	private String telephone;
	private double outstandingBalance;

    // Added Data Storage Getter and Setter
	public void setId(long id) {
    	this.id = id;
	}
    
	public long getId() {
    	return id;
	}
    
    // Added Data Storage Getter and Setter
	public String getFirstName() {
   	 return firstName;
	}
    
	public void setFirstName(String firstName) {
   	 this.firstName = firstName;
	}
    
    // Added Data Storage Getter and Setter
	public String getLastName() {
   	 return lastName;
	}
    
	public void setLastName(String lastName) {
   	 this.lastName = lastName;
	}
    
    // Added Data Storage Getter and Setter
	public String getAddress() {
   	 return address;
	}
    
	public void setAddress(String address) {
   	 this.address = address;
	}
    
    // Added Data Storage Getter and Setter
	public String getTelephone() {
   	 return telephone;
	}
    
	public void setTelephone(String telephone) {
   	 this.telephone = telephone;
	}
    
    // Added Data Storage Getter and Setter
	public double getOutstandingBalance() {
   	 return outstandingBalance;
	}
    
	public void setOutstandingBalance(double outstandingBalance) {
   	 this.outstandingBalance = outstandingBalance;
	}
}

 Step 3: Finally, we need to introduce a repository class that connects clients to a database (which we haven’t chosen yet). Here is that class for client:

@Repository
public interface ClientRepository extends CrudRepository<Client, Long>  {
}

Yes, that’s all that is needed! Spring Boot has automatically created all the CRUD operations for us.

Try It Out For Yourself!

To practice what we've just done, go ahead and make similar modifications to the other model classes (reservation, pilot, plane). The key here is determining which parts of the existing Java classes are indeed the model (state) portions, and which parts belong somewhere else. Then you again refactor the code to have the model objects rely on the persistence layer to do the persistence work.

You can check out the solution for this exercise in this GitHub project. 

We have completed the second set of steps in our MVC modification. We have identified code that will not be easily modified, tested, or maintained. We have introduced model and persistence layers. And we leveraged the Spring Boot framework to make the work simpler.

Let's Recap! 

  • The model holds the state of the application.

    • The model is made up of entities.

    • The storage of model information should be abstracted.

  • Abstract the model by introducing model and persistence layers. 

  • Use an MVC framework to simplify your work. 

Now that we've abstracted our our model, we're ready to work on our controller!  

Example of certificate of achievement
Example of certificate of achievement