GUI Architectures:

 history, present,

the Web

Milko Kosturkov

  • Developer for over 15 years
  • fullstack
  • bachelor in Electronics
  • master in IT
  • contractor
  • Ty's Software
  • Bulgaria PHP Conference 2019 Organizer

Fields I've worked in

SMS and Voice services

MMO Games

local positioning systems 

TV productions

Healthcare

e-commerce

websites

SEO

online video

Why this talk?

  • We need to know our past
  • Mitigate confusion
  • Present some other concepts
  • "Whatsa Controller Anyway"                                                                              - Kyle Brown

MVC: The history

Flashback

  • invented in 1979
  • by Trygve Reenskaug
  • while with Xerox
  • NOT FOR THE WEB
  • for GUI applications
  • with Smalltalk - 80
  • for the "Dynabook"

By Trygve Reenskaug - Trygve Reenskaug, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=31168223

MVC was conceived as a general solution to the problem of users controlling a large and complex data set.

Smalltalk GUI

MVC: Concepts

 A Thing

According to Trygve Reenskaug

Something that is of interest to the user. It could be concrete, like a house or an integrated

circuit. It could be abstract, like a new idea or opinions about a paper. It could be a whole,

like a computer, or a part, like a circuit element

Model

According to Trygve Reenskaug

Models represent knowledge. A model could be a single object (rather uninteresting), or it

could be some structure of objects.

There should be a one-to-one correspondence between the model and its parts on the one hand, and the represented world as perceived by the owner of the model on the other hand

View

According to Trygve Reenskaug

A view is attached to its model (or model part) and gets the data necessary for the presentation

from the model by asking questions...

A view is a (visual) representation of its model. It would ordinarily highlight certain attributes of the model and suppress others

... the view will therefore have to know the semantics of the attributes of the model it represents.

Controllers

According to Trygve Reenskaug

A controller is the link between a user and the system. It provides the user with input by

arranging for relevant views to present themselves in appropriate places on the screen. It

provides means for user output by presenting the user with menus or other means of giving

commands and data.

A controller should never supplement the views...

Conversely, a view should never know about user input, such as mouse operations and

keystrokes.

MVC

MVC

MVC: Shortcomings

Problem: No place to put presentation logic

Solution: Application Model

VisualWorks problem from 1993

Application Model MVC

Problem: Controller does not communicate with view

Problem: Windows widgets make Controller useless

Dolphin Smalltalk problems from 1995

Solution: "Twisting the Triad", Andy Bower, Blair McGlashan

Model View Presenter

The Taligent Programming Model for C++ and Java

  • A new take on MVC
  • Implemented as a framework
  • A paper by Mike Potel, VP & CTO of Taligent
  • Written in 1996

Model View Presenter

The triad breakdown

Model View Presenter

Client/Server

GUI vs Web Apps

  • GUI
    • long run duration
    • continuous input (clicks, keyboard strokes)
    • continuous output (multiple view updates)
  • Web
    • short run duration (one request/response cycle)
    • one time input
    • one time output
  • Are Apps
  • Present GUI to the user

Meanwhile on the 90s-00s Web...

PERL Guestbook

#!/usr/bin/perl

use DBI;
use URI::Escape;
use CGI qw(:standard);
use CGI::Carp 'fatalsToBrowser';
use Constants;
  
print "Content-type: text/html; charset=".Constants::CHARSET."\r\n\r\n";

use ConnectDB;
use GetSetting;

$title = 'Guestbook - Perl Guestbook';
$description = '';
$keywords = '';
  
if($ENV{'HTTP_X_FORWARDED_FOR'}) 
{
   $ip = $ENV{'HTTP_X_FORWARDED_FOR'};
} 
else 
{
   $ip = $ENV{'REMOTE_ADDR'};
}

require "lib/_function.pl" or die('Cannot open the file "_function.pl"');

require "lib/_top.pl" or die('"_top.pl"');

my %GET;
my @pairs = split(/&/, $ENV{ "QUERY_STRING" });

foreach (@pairs)
{
   my ($name, $value) = split(/=/, $_);
   $GET{$name} = $value;
   $value = uri_unescape($value);
}

my $page = int($GET{'page'});
if($page == 0) { $page = 1; }
if($row_setting->{number_post} == 0) { $row_setting->{number_post} = 10; }
$begin = ($page - 1)*$row_setting->{number_post};

print <<HTML1;
<table width="80%" cellpadding="0" cellspacing="0" border="0">
<tr>
  <td><center>
      <br>
      <table border="0">
        <tr>
          <td><img src="http://$ENV{ "SERVER_NAME" }/images/notepad.gif" width="16" height="16" border="0"></td>
          <td><a title="Add message" href="add.pl">Add message</a></td>
        </tr>
      </table>
    </center>
    <br>
HTML1

my $query = $dbh->prepare("SELECT * FROM ".Constants::DB_MSG." WHERE hide = 'show' ORDER BY time DESC LIMIT $begin, ".$row_setting->{number_post}."");
$query->execute() or die("Error executing SQL query!");
 
while($row = $query->fetchrow_hashref())
{ 
   my $name   = $row->{name};
   my $msg    = $row->{msg};
   my $city   = $row->{city};
   my $email  = $row->{email};
   my $url    = $row->{url};
   my $ip     = $row->{ip};
   my $answer = $row->{answer};
   my $time   = $row->{time};
  
   $name   =~ s/^\s+|\s+$//g;
   $msg    =~ s/^\s+|\s+$//g;
   $city   =~ s/^\s+|\s+$//g;
   $email  =~ s/^\s+|\s+$//g;
   $url    =~ s/^\s+|\s+$//g;
   $ip     =~ s/^\s+|\s+$//g;
   $answer =~ s/^\s+|\s+$//g;
   $time   =~ s/^\s+|\s+$//g;
  
   if($row_setting->{smile} eq 'yes')
   {
      $msg =~ s/\[:\)\)\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_biggrin.gif">/g;
      $msg =~ s/\[:~\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_confused.gif">/g;
      $msg =~ s/\[:\)\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_cool.gif">/g;
      $msg =~ s/\[:\(\|\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_mad.gif">/g;
      $msg =~ s/\[:\|\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_eek.gif">/g;
      $msg =~ s/\[:\(\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_frown.gif">/g;
      $msg =~ s/\[:\(\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_frown.gif">/g;
      $msg =~ s/\[:\|\)\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_smile.gif">/g;
      $msg =~ s/\[:\/\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_wink.gif">/g;
      $msg =~ s/\[:\(\)\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_razz.gif">/g;
      $msg =~ s/\[:\/~\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_blush.gif">/g;
      $msg =~ s/\[:\/\(\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_cray.gif">/g;
      $msg =~ s/\[:\)\(\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_dance.gif">/g;
      $msg =~ s/\[:\|_\|\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_drinks.gif">/g;
      $msg =~ s/\[:\?\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_fool.gif">/g;
      $msg =~ s/\[:\)\|\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_good.gif">/g;
      $msg =~ s/\[:\@\@\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_kiss_mini.gif">/g;
      $msg =~ s/\[:\)-\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_man_in_love.gif">/g;
      $msg =~ s/\[:-\@\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_rolleyes.gif">/g;
      $msg =~ s/\[:\|\|\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_scratch.gif">/g;
      $msg =~ s/\[:\@\|\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_shok.gif">/g;
      $msg =~ s/\[:\@\(\|\)\]/<IMG border=0 src="http:\/\/$ENV{ "SERVER_NAME" }\/smile\/icon_shout.gif">/g;
   }
   else
   {
      $msg =~ s/\[:\)\)\]//g;
      $msg =~ s/\[:~\]//g;
      $msg =~ s/\[:\)\]//g;
      $msg =~ s/\[:\(\|\]//g;
      $msg =~ s/\[:\|\]//g;
      $msg =~ s/\[:\(\]//g;
      $msg =~ s/\[:\(\]//g;
      $msg =~ s/\[:\|\)\]//g;
      $msg =~ s/\[:\/\]//g;
      $msg =~ s/\[:\(\)\]//g;
      $msg =~ s/\[:\/~\]//g;
      $msg =~ s/\[:\/\(\]//g;
      $msg =~ s/\[:\)\(\]//g;
      $msg =~ s/\[:\|_\|\]//g;
      $msg =~ s/\[:\?\]//g;
      $msg =~ s/\[:\)\|\]//g;
      $msg =~ s/\[:\@\@\]//g;
      $msg =~ s/\[:\)-\]//g;
      $msg =~ s/\[:-\@\]//g;
      $msg =~ s/\[:\|\|\]//g;
      $msg =~ s/\[:\@\|\]//g;
      $msg =~ s/\[:\@\(\|\)\]//g;
   }

print <<HTML2;
<table class="cattab" border="0" width="100%">
<tr>
  <td class="menu"><table border="0" width="100%">
    <tr>
      <td width="50%"><p><b>$name</b><br>
HTML2

   if($city ne '') { print "City: $city <br>"; }
   if($email ne '') { print "E-mail: $email <br>"; }
   if($url ne '') { print "Website: <a class=link href='$url'>$url</a>"; }

print <<HTML3; 
</td>

<td width="50%"><p align="right"><i>added: $time</i></td>
</tr>
</table>
</td>
</tr>
<tr>
  <td height="36"><p> 
HTML3
   
   print "$msg";

   if($answer ne '' && $answer ne '-') 
   {
      print "<font color=\#FF0000><p>Admin: $answer</p></font>";
   } 

   print '</p></td></tr></table>';

}

print <<HTML4; 
<center>
  <br>
  <table border="0">
    <tr>
      <td><img src="http://$ENV{ "SERVER_NAME" }/images/notepad.gif" width="16" height="16" border="0"></td>
      <td><a title="Add message" href="add.pl">Add message</a></td>
    </tr>
  </table>
</center>
HTML4

$query->finish();

my $query = $dbh->prepare("SELECT count(*) FROM ".Constants::DB_MSG." WHERE hide = 'show'");
$query->execute() or die("Error executing SQL query!");
my $count = $query->fetchrow_arrayref()->[0];
  
$query->finish();
  
my $number = int(($count - 1) / $row_setting->{number_post}) + 1;

if($page != 1)
{  
   $pervpage = '<a href=http://'.$ENV{ "SERVER_NAME" }.$ENV{ "SCRIPT_NAME" }.'?page=1>&lt;&lt;</a> 
                <a href=http://'.$ENV{ "SERVER_NAME" }.$ENV{ "SCRIPT_NAME" }.'?page='.($page - 1).'>&lt;</a> '; 
}

if($page != $number)
{ 
   $nextpage = ' <a href=http://'.$ENV{ "SERVER_NAME" }.$ENV{ "SCRIPT_NAME" }.'?page='.($page + 1).'>&gt;</a> 
                <a href=http://'.$ENV{ "SERVER_NAME" }.$ENV{ "SCRIPT_NAME" }.'?page='.$number.'>&gt;&gt;</a>'; 
}

if(($page - 2) > 0) { $page2left = '<a href=http://'.$ENV{ "SERVER_NAME" }.$ENV{ "SCRIPT_NAME" }.'?page='.($page - 2).'>...'.($page - 2).'</a> | '; }
if(($page - 1) > 0) { $page1left = '<a href=http://'.$ENV{ "SERVER_NAME" }.$ENV{ "SCRIPT_NAME" }.'?page='.($page - 1).'>'.($page - 1).'</a> | '; }
if(($page + 2) <= $number) { $page2right = ' | <a href=http://'.$ENV{ "SERVER_NAME" }.$ENV{ "SCRIPT_NAME" }.'?page='.($page + 2).'>'.($page + 2).'...</a>'; }
if(($page + 1) <= $number) { $page1right = ' | <a href=http://'.$ENV{ "SERVER_NAME" }.$ENV{ "SCRIPT_NAME" }.'?page='.($page + 1).'>'.($page + 1).'</a>'; }

print "<p>Number of messages: [$count]   Pages:  ";
print $pervpage.$page2left.$page1left.'<b>'.$page.'</b>'.$page1right.$page2right.$nextpage.'</p>';

require 'lib/_bottom.pl' or die('Canot open the file "_bottom.pl"');

$dbh->disconnect();

PHP Guestbook

<table width="400" border="0" align="center" cellpadding="3" cellspacing="0">
<tr>
<td><strong>View Guestbook | <a href="guestbook.php">Sign Guestbook</a> </strong></td>
</tr>
</table>
<br>

<?php

$host="localhost"; // Host name
$username=""; // Mysql username
$password=""; // Mysql password
$db_name="test"; // Database name
$tbl_name="guestbook"; // Table name

// Connect to server and select database.
mysql_connect("$host", "$username", "$password")or die("cannot connect server ");
mysql_select_db("$db_name")or die("cannot select DB");

$sql="SELECT * FROM $tbl_name";
$result=mysql_query($sql);

while($rows=mysql_fetch_array($result)){
?>

<table width="400" border="0" align="center" cellpadding="0" cellspacing="1" bgcolor="#CCCCCC">
<tr>
<td><table width="400" border="0" cellpadding="3" cellspacing="1" bgcolor="#FFFFFF">
<tr>
<td>ID</td>
<td>:</td>
<td><? echo $rows['id']; ?></td>
</tr>
<tr>
<td width="117">Name</td>
<td width="14">:</td>
<td width="357"><? echo $rows['name']; ?></td>
</tr>
<tr>
<td>Email</td>
<td>:</td>
<td><? echo $rows['email']; ?></td>
</tr>
<tr>
<td valign="top">Comment</td>
<td valign="top">:</td>
<td><? echo $rows['comment']; ?></td>
</tr>
<tr>
<td valign="top">Date/Time </td>
<td valign="top">:</td>
<td><? echo $rows['datetime']; ?></td>
</tr>
</table></td>
</tr>
</table>

<?php
}
mysql_close(); //close database
?>

JSP Example

<html>
<head>
  <title>Book Query</title>
</head>
<body>
  <h1>Another E-Bookstore</h1>
  <h3>Choose Author(s):</h3>
  <form method="get">
    <input type="checkbox" name="author" value="Tan Ah Teck">Tan
    <input type="checkbox" name="author" value="Mohd Ali">Ali
    <input type="checkbox" name="author" value="Kumar">Kumar
    <input type="submit" value="Query">
  </form>
 
  <%
    String[] authors = request.getParameterValues("author");
    if (authors != null) {
  %>
  <%@ page import = "java.sql.*" %>
  <%
      Connection conn = DriverManager.getConnection(
          "jdbc:mysql://localhost:8888/ebookshop", "myuser", "xxxx"); // <== Check!
      // Connection conn =
      //    DriverManager.getConnection("jdbc:odbc:eshopODBC");  // Access
      Statement stmt = conn.createStatement();
 
      String sqlStr = "SELECT * FROM books WHERE author IN (";
      sqlStr += "'" + authors[0] + "'";  // First author
      for (int i = 1; i < authors.length; ++i) {
         sqlStr += ", '" + authors[i] + "'";  // Subsequent authors need a leading commas
      }
      sqlStr += ") AND qty > 0 ORDER BY author ASC, title ASC";
 
      // for debugging
      System.out.println("Query statement is " + sqlStr);
      ResultSet rset = stmt.executeQuery(sqlStr);
  %>
      <hr>
      <form method="get" action="order.jsp">
        <table border=1 cellpadding=5>
          <tr>
            <th>Order</th>
            <th>Author</th>
            <th>Title</th>
            <th>Price</th>
            <th>Qty</th>
          </tr>
  <%
      while (rset.next()) {
        int id = rset.getInt("id");
  %>
          <tr>
            <td><input type="checkbox" name="id" value="<%= id %>"></td>
            <td><%= rset.getString("author") %></td>
            <td><%= rset.getString("title") %></td>
            <td>$<%= rset.getInt("price") %></td>
            <td><%= rset.getInt("qty") %></td>
          </tr>
  <%
      }
  %>
        </table>
        <br>
        <input type="submit" value="Order">
        <input type="reset" value="Clear">
      </form>
      <a href="<%= request.getRequestURI() %>"><h3>Back</h3></a>
  <%
      rset.close();
      stmt.close();
      conn.close();
    }
  %>
</body>
</html>

JSP Model 2

Music Without Borders
  • Understanding JavaServer Pages Model 2 architecture - an article by Govind Seshadri in JavaWorld in 1999
  • Coined the term MVC
  • Used a Vector for a model and put all logic for manipulating the data in it in the Controller
  • A terrible example, with terrible code
import java.util.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import shopping.CD;
public class ShoppingServlet extends HttpServlet {
  public void init(ServletConfig conf) throws ServletException  {
    super.init(conf);
  }
  public void doPost (HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException {
    HttpSession session = req.getSession(false);
    if (session == null) {
      res.sendRedirect("http://localhost:8080/error.html");
    }
    Vector buylist=
      (Vector)session.getValue("shopping.shoppingcart");
    String action = req.getParameter("action");
    if (!action.equals("CHECKOUT")) {
      if (action.equals("DELETE")) {
        String del = req.getParameter("delindex");
        int d = (new Integer(del)).intValue();
        buylist.removeElementAt(d);
      } else if (action.equals("ADD")) {
        //any previous buys of same cd?
        boolean match=false;
        CD aCD = getCD(req);
        if (buylist==null) {
          //add first cd to the cart
          buylist = new Vector(); //first order
          buylist.addElement(aCD);
        } else { // not first buy
          for (int i=0; i< buylist.size(); i++) {
            CD cd = (CD) buylist.elementAt(i);
            if (cd.getAlbum().equals(aCD.getAlbum())) {
              cd.setQuantity(cd.getQuantity()+aCD.getQuantity());
              buylist.setElementAt(cd,i);
              match = true;
            } //end of if name matches
          } // end of for
          if (!match) 
            buylist.addElement(aCD);
        }
      }
      session.putValue("shopping.shoppingcart", buylist);
      String url="/jsp/shopping/EShop.jsp";
      ServletContext sc = getServletContext();
      RequestDispatcher rd = sc.getRequestDispatcher(url);
      rd.forward(req, res);
    } else if (action.equals("CHECKOUT"))  {
      float total =0;
      for (int i=0; i< buylist.size();i++) {
        CD anOrder = (CD) buylist.elementAt(i);
        float price= anOrder.getPrice();
        int qty = anOrder.getQuantity();
        total += (price * qty);
      }
      total += 0.005;
      String amount = new Float(total).toString();
      int n = amount.indexOf('.');
      amount = amount.substring(0,n+3);
      req.setAttribute("amount",amount);
      String url="/jsp/shopping/Checkout.jsp";
      ServletContext sc = getServletContext();
      RequestDispatcher rd = sc.getRequestDispatcher(url);
      rd.forward(req,res);
    }
  }
  private CD getCD(HttpServletRequest req) {
    //imagine if all this was in a scriptlet...ugly, eh?
    String myCd = req.getParameter("CD");
    String qty = req.getParameter("qty");
    StringTokenizer t = new StringTokenizer(myCd,"|");
    String album= t.nextToken();
    String artist = t.nextToken();
    String country = t.nextToken();
    String price = t.nextToken();
    price = price.replace('$',' ').trim();
    CD cd = new CD();
    cd.setAlbum(album);
    cd.setArtist(artist);
    cd.setCountry(country);
    cd.setPrice((new Float(price)).floatValue());
    cd.setQuantity((new Integer(qty)).intValue());
    return cd;
  }
}

Struts and MVC2

Controller Problems

  • Poor separation of concerns
  • Business logic lives in it
  • Does a lot of things...
    • ... has a lot of dependencies...
    • ... becomes really fat...
    • ... and hard to maintain.
  • Is a GOD class

Not All That Bad

  • Extract the domain logic in a command(s)/use cases/application services
  • Use the Servlet/Controller action as Presenter - hold presentation logic in it only
  • Leave the view as is
  • If the presenter gets too fat - breakdown in smaller MVP components/modules/widgets

Could be done with any of today's PHP "MVC" frameworks

Modern SPA apps

  • Presenter is fully into the client
  • Server holds the model parts:
    • Commands and the rest - RPC, SOAP, custom APIs
    • Selections and models - REST, GraphQL
  • HTTP and JSON/XML/Other are only data transfer protocol and formats
  • No need for complex presenters/modules/widgets

Presentation Model

  • Martin Fowler, mid 00s, probably 2004
  • No "Controller" - handled for us by the environment
  • Variation of VisualWorks' Application Model
  • Con - synchronization between Presentation Model and view - simple, but boring and repetative 

Model View ViewModel

  • By John Gossman from Microsoft in 2005
  • Exactly like Presentation Model, but with sync (data-binding) done by the framework
  • Martin Fowler predicted it :)
  • Used in Windows Presentation Foundation and Silverlight...
  • ... Knockout, React, Vue, etc.

Resources Links

Thank you!

Milko Kosturkov

@mkosturkov

linkedin.com/in/milko-kosturkov

mailto: mkosturkov@gmail.com

 

These slides:

https://slides.com/milkokosturkov/gui-architectures-past-present-web