Sunday, December 29, 2013

A Real World Example of GWT 2.x, MySQL, and MVP: Part 1

Part 1: Getting Started
Part 2: Putting the Framework Together
Part 3: Finally Making the Page Content
Part 4: User Authentication

Getting Started


Let's drop all pretenses and games.  You're done playing around, you are here to make an actual website and all you can find is little dinky tutorials that get you halfway there.  Well if you got the time then I got the rhyme... or more specifically a step-by-step guide to help you put all of the pieces together.

This is a continuation of my previous blogs, GWT 2.x and MySQL and Doing More with GWT 2.x and MySQL.  If you haven't read through, fiddled around, and fully understood these pages yet (including the humiliating typos and grammatical errors) then please go back and take your time as the rest of us will wait for you.  I also assume you have some passing familiarity with the MVP framework, and if not please read GWT MVP Architecture Tutorial otherwise you'll be more lost than that time when your momma got lost and they had to use all four sides of the milk carton because she was so fat. Boo-yah!


Planning Your Project

The most important phase of any development cycle is the planning phase.  The more time you spend planning the less time you will spend in the later coding and maintenance phases.  We will start out by asking what do we want to solve?  Who are our customers?  What do our customers want?  Are Reese's Peanut Butter Cups are better than York Peppermint Patties?  Is a website created with GWT the correct tool to solve this problem or provide this service?

Let's say my mom isn't very computer knowledgeable and wants to know the best way to search for something.  Ah ha!  A service or need must be filled by the market!!!  (Forget that thisthis, or this exists!)  For our example I will create a simple website that will rank and link to the different search engines out there. And because we will use the MVP framework for our project will be able to scale the website later on as it grows and adds more functionality.  So by very carefully asking questions that do not "lead" my theoretical mom to a conclusion I ask her things like, "What would this site look like to you?", "What sort of things would it do?", or "Why isn't dinner made yet?"  And from my customer interview(s) about the website functionality we come to a consensus:
  • The layout will have a header for home, about, and login/logout buttons.
  • The name and a link to the website
  • Categories for the type of search engine
  • A ranking value for each site
  • I'm her favorite son
  • A login page for users
  • A registration page for new uers
  • An edit search engine page for administrators 
  • Remove a search engine from the list for administrators
  • Add new search engine page
Now that we have gathered the wants and needs of my customer(s) we now create a basic mockups for every page of my entire website.  Using a program like Balsamiq Mockups we can create an idea of what the home page might look like and use as a reference when we go to build the page.


Finally I take all the mockups to my mom and ask questions about each individual page that again don't lead her to what I want her to say but that allow her to come to her own conclusions.  So I ask her things like, "What do you see when you look at this page?", "What do you expect to happen when you click this button or link?", "And seriously why the fuck is dinner not made yet?"

Getting Your Project Started

In Eclipse I've created a new GWT Project with the name "SearchEngines" and a the package name "com.example.searchengines". The first thing I have done is remove the starter classes "GreetingService", "GreetingServiceAsync", "GreetingServiceImpl", and "FieldVerifyer", as well as removing any servlet reference to them in the web.xml file.

Following the MVP framework I am going to create the following packages in my project's "src" folder:

com.example.searchengines.client.event
The client's event package will contain classes that will handle our custom events

com.example.searchengines.client.presenter
The client's presenter package will have our presenter classes which hold the business logic of the page and works with a companion view class.

com.example.searchengines.client.service
The client's service package is my own personal preference of placing the services in their own subfolder since larger projects can have many services and clutter the client package.

com.example.searchengines.client.view
The client's view package contains the view classes which hold the HTML layout of the page and works with a companion presenter class.

com.example.searchengines.client.widget
A client's widget package is my own personal preference of placing all of the custom widgets you will collect on the web (like a super-cool star rating widget) or create on your own.

The final steps to setting up the project is to very carefully follow my tutorial on GWT 2.x and MySQL which will give us the ability to connect to and query a MySQL database.  The only thing to note is the interfaces DBConnection and DBConnectionAsync should be in the "com.example.searchengines.client.service" package and not the "com.example.searchengines.client" package as described in the tutorial.  When you are finished following the tutorial your project should look like this:


Setting Up Your Database

Now that our project is set up let's add a MySQL database it can connect to.  First you must download and install a MySQL server on your computer from this site MySQL Community Edition Downloads and I recommend also downloading MySQL Workbench to manage and browse your database which you can get here Download MySQL Workbench.  Eventually when you publish your site to your webserver you will also need to install MySQL there as well.

Open MySQL Workbench and you should see "Local Instance of MySQL" in the "SQL Development" section of the home screen which will open a connection to start querying your local MySQL database.  Once the new tab for your local instance of MySQL opens create your new schema, for our example we will name it "moms_search_engines" which will now appear in the "Object Browser".  If your new schema is not bold then either double-click it or right-click it and choose "Set as Default Schema".  Now expand it and create some tables that will hold your website's information.  If you are developing your own schema you have to think very carefully about this before you begin and I suggest reading as many books and articles about database normalization, best practices, and what to avoid.  I cannot stress this enough because once you go down the path of building your schema and once you roll it out to into production it becomes the biggest hassle to re-factor it.  For our example you can use the SQL commands below to create 3 tables just to get us started.

You can step through the SQL manually or download a SQL script here. Sure go ahead and download it, but it won't give you the satisfaction of creating something by yourself.

Table 1: categories
First we will start with the categories table, which is a list of the types of search engines.
CREATE TABLE `categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Unique identifier',
  `name` varchar(45) DEFAULT NULL COMMENT 'Category name',
  `description` varchar(255) DEFAULT NULL COMMENT 'Category description',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='Search engine categories';

Table 2: search_engines
The search_engines table will hold our list of search engines as well as a url link to their site, a category for filtering, a rank value, and if they are active.
CREATE TABLE `search_engines` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Unique identifier',
  `name` varchar(45) DEFAULT NULL COMMENT 'Search provider name',
  `url` varchar(255) DEFAULT NULL COMMENT 'Search provider url',
  `category_id` int(11) DEFAULT '0' COMMENT 'Search engine category',
  `rating` int(2) DEFAULT '0' COMMENT 'Rating value from 1-10',
  `is_active` tinyint(1) DEFAULT '1' COMMENT 'Is the search provider active?',
  PRIMARY KEY (`id`),
  KEY `fk_search_engines_category` (`category_id`),
  CONSTRAINT `fk_search_engines_category` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB COMMENT='An internet search provider';

Now that we have our tables it is good to get some sample data to get us started.  Nothing major but enough to give us a good idea of how our pages look once we start building running them.  For our example you can use some sample data like below:
INSERT INTO `categories` VALUES 
  (1,'web','Web searches'),
  (2,'metasearch','Metasearch'),
  (3,'food','Foods or recipes'),
  (4,'job','Employment openings'),
  (5,'legal','Case law search'),
  (6,'medical','Medical paper and news');

INSERT INTO `search_engines` VALUES 
  (1,'Google','http://www.google.com',1,9,TRUE),
  (2,'Bing','http://www.bing.com',1,5,TRUE),
  (3,'DuckDuckGo','http://www.duckduckgo.com',1,8,TRUE),
  (4,'AltaVista','http://www.altavista.com',1,4,FALSE),
  (5,'Yahoo!','http://search.yahoo.com',1,6,TRUE),
  (6,'Excite','http://msxml.excite.com',2,7,TRUE),
  (7,'Kayak','http://www.kayak.com',2,9,TRUE),
  (8,'RecipeBridge','http://www.recipebridge.com',3,7,TRUE),
  (9,'Yummly','http://yummly.com',3,6,TRUE),
  (10,'CareerBuilder','http://www.careerbuilder.com',4,5,TRUE),
  (11,'Yahoo! HotJobs','http://www.hotjobs.com',4,5,TRUE),
  (12,'Monster','http://www.monster.com',4,4,TRUE),
  (13,'LexisNexis','http://www.lexisnexis.com',5,9,TRUE),
  (14,'Google Scholar','http://scholar.google.com',5,7,TRUE),
  (15,'WebMD','http://www.webmd.com',6,9,TRUE),
  (16,'Quertle','http://www.quertle.info',6,6,TRUE);

If you read my previous blogs on GWT 2.x and MySQL (and you should have at this point) you should know by now that we are going to create a class in the client's shared package for each table we create, and those classes will have properties to match the table columns.  So the table "search_engines" will have a class called "SearchEngine" and have properties for the id, name, url, category_id, rating, and is_active.

SearchEngine.java

package com.example.searchengines.shared;

import com.google.gwt.user.client.rpc.IsSerializable;

/**
* An internet search provider
*/
public class SearchEngine implements IsSerializable {

    private double id = 0.0d;
    private String name = "";
    private String url = "";
    private Category category = null;
    private int rating = 0;
    private boolean isActive = true;
    

    /**
     * An internet search provider
     */
    public SearchEngine() {
    }


    /**
    * Unique identifier
    * @param id the id to set
    */
    public void setId(double id) {
        this.id = id;
    }


    /**
    * Unique identifier
    * @return the id
    */
    public double getId() {
        return id;
    }


    /**
     * Search provider name
     * @return the name
     */
    public String getName() {
        return name;
    }


    /**
     * Search provider name
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }


    /**
     * Search provider url
     * @return the url
     */
    public String getUrl() {
        return url;
    }


    /**
     * Search provider url
     * @param url the url to set
     */
    public void setUrl(String url) {
        this.url = url;
    }


    /**
     * Search engine category
     * @return the category
     */
    public Category getCategory() {
        return category;
    }


    /**
     * Search engine category
     * @param category the category to set
     */
    public void setCategory(Category category) {
        this.category = category;
    }


    /**
     * Rating value from 1-10
     * @return the rating
     */
    public int getRating() {
        return rating;
    }


    /**
     * Rating value from 1-10
     * @param rating the rating to set
     */
    public void setRating(int rating) {
        this.rating = rating;
    }


    /**
     * Is the search provider active?
     * @return the isActive
     */
    public boolean isActive() {
        return isActive;
    }


    /**
     * Is the search provider active?
     * @param isActive the isActive to set
     */
    public void setActive(boolean isActive) {
        this.isActive = isActive;
    }
}

For the categories, since they are far more static than the other tables in your database, I sometimes find it easier to make it an enum.  The only challenge to this is, unlike the .NET Framework or later versions of Java, enums do not have values and you have to add some methods to do some conversions.  I did one the hard way and another the easy way and it's up to you how you want to approach the value conversion depending on how big your enum is.  Also in order to use the enum in GWT in both your client, service, and server, you have to implement IsSerializable.

Category.java

package com.example.searchengines.shared;

import com.google.gwt.user.client.rpc.IsSerializable;


/**
* Search category
*/
public enum Category implements IsSerializable {
    /**
     * Web searches
     */
    web,
    /**
     * Meta-search
     */
    metasearch,
    /**
     * Foods or recipes
     */
    food,
    /**
     * Employment openings
     */
    job,
    /**
     * Case law search
     */
    legal,
    /**
     * Medical paper and news
     */
    medical;
    
    
    
    /**
     * Return Category from integer
     * @param category Search category
     * @return
     */
    public static Category fromInteger(int category) {
        switch(category) {
            case 1:
                return web;
            case 2:
                return metasearch;
            case 3:
                return food;
            case 4:
                return job;
            case 5:
                return legal;
            case 6:
                return medical;   
            default:
                return null;
        }
    }
    
    
    /**
     * Return Category to integer
     * @param category Search category
     * @return
     */
    public static int toInteger(Category category) {
        int index = 0;
        Category[] categories = Category.values();
        int i = index;
        while (i < categories.length && index == 0) {
            if (categories[i].equals(category))
                index = i;
            i++;
        }
        categories = null;
        return index;
    }
}

One thing you might notice is I've commented everything and that's because as a developer that's part of your damn job! It make take a few extra minutes of your day but do you honestly think that 2 years from now when you look at this code that you're going to remember any of this shit?  Or if you are lucky enough to build a site that matures and you get to hand it off to some code monkey shmuck who will maintain it while you are off building your next super-cool website, you damn well better have comments because it will make you look good and will help the monkey in his job.  So pay it forward and comment your shit.

Adding database users


You wouldn't want your GWT MySQL connection to your database as root, that's just not smart stuff man.  I will create 2 uers, both named "mom", one for the host "%" and "localhost", and granted "SELECT, INSERT, UPDATE, DELETE" on the schema, "moms_search_engines". Give the user "mom" a good strong password, you wouldn't want your mom to be hacked now would you.

CREATE USER 'mom'@'%' IDENTIFIED BY 'mom';
GRANT SELECT,INSERT,UPDATE,DELETE ON moms_search_engines TO 'mom'@'%';

CREATE USER 'mom'@'localhost' IDENTIFIED BY 'mom';
GRANT SELECT,INSERT,UPDATE,DELETE ON moms_search_engines TO 'mom'@'localhost';

FLUSH PRIVILEGES;

And finally update your MySQLConnection class with your new "mom" user and her super-strong password you decided on.

package com.example.searchengines.server;

public class MySQLConnection implements DBConnection {

    private String _user = "mom";
    private String _pass = "mom";


Okay cool we're ready for the next part:
Part 2: Putting the Framework Together

4 comments:

  1. where can i read the rest of it?

    ReplyDelete
    Replies
    1. I added links to the other parts of the tutorial. I should have thought of that earlier. Sorry.

      Delete
    2. Hi Austin. I am following your tutorial for my project. Is it possible to email you directly to ask you questions.

      Delete