database.sarang.net
UserID
Passwd
Database
ㆍDBMS
MySQL
PostgreSQL
Firebird
Oracle
Informix
Sybase
MS-SQL
DB2
Cache
CUBRID
LDAP
ALTIBASE
Tibero
DB 문서들
스터디
Community
공지사항
자유게시판
구인|구직
DSN 갤러리
도움주신분들
Admin
운영게시판
최근게시물
DBMS Columns 873 게시물 읽기
 News | Q&A | Columns | Tutorials | Devel | Files | Links
No. 873
An introduction to object prevalence
작성자
정재익(advance)
작성일
2003-10-30 10:46
조회수
20,956

Unleash the power of object orientation with a better persistence strategy

 

Level: Intermediate

Carlos Eduardo Villela (carlos@stage2.com.br)
Developer and owner, Stage2 Sistemas Web
August 1, 2002

Persisting state and data has always been a problem with object-oriented software. Over the years, developers have stored object data in many ways, including relational databases, flat files,and XML. None of these approaches really managed to keep the software purely object-oriented. The Prevayler team is changing this with the object prevalence concept. This article introduces object prevalence.

Note: You will find it helpful to be familiar with the concept of a Command Object. The subject is very well covered in the literature on Design Patterns.

Why persistence as you know it is bad
Today, data persistence for object-oriented systems is an incredibly cumbersome task to deal with when building many kinds of applications. The developer must map objects to database tables, XML files or use some other non-OO way to represent data, destroying encapsulation completely. One solution to this problem is object prevalence.

The Prevalent way
Object prevalence is a concept that was developed by Klaus Wuestefeld and some colleagues at Objective Solutions. Its first implementation, known as Prevayler, became available in November 2001 as an open-source project. (See Resources.) Today, Prevayler is at version 1.3.0 and has about 350 lines of code. You may think that the code is too small to do anything useful but, based upon my experience on a recent project, I can confirm that Prevayler is several orders of magnitude faster than one of the leading open source relational databases. It is all about simplicity.

Object prevalence is inherently and conceptually simple and can be implemented in any object-oriented programming language that can serialize an object -- a feature in many modern OO languages.

In a prevalent system, everything is kept in RAM, as though you were just using a programming language. Serializable command objects manage all the necessary changes required for persistence. Queries are run against pure Java language objects, giving developers all the flexibility of the Collections API and other APIs, such as the Jakarta Commons Collections and Jutil.org. (See Resources.)

Figure 1. How a prevalent system works
How a prevalent system works

Before changes are applied to business objects, each command is serialized and written to a log file (Figure 1). Then, each command is executed immediately. Optionally, in a low-use period, the system can take a snapshot of the business objects, aggregating all the commands applied into one large file to save some reloading time.

During system recovery after a shutdown or system crash, the system retrieves its last saved state from the snapshot file (if one is available) and then reads the commands from the log files created since the snapshot was taken. These commands are applied to the business objects exactly as if they had just come from the system's clients, including the system clock. Thus, the system returns to the state it was in just before the shutdown and is ready to run.

For a prevalent system to work correctly, its business objects must follow two very simple rules. The business objects must be:

  • Serializable - At any point in time, the system might want to persist an object to disk or other non-volatile media.
  • Deterministic - Given some input, the business object's methods must always return the same output.

This is particularly important when a business object deals with the system clock. Object orientation gurus often say that the system clock is an external actor to the system. Prevayler helps you think this way by providing an adapter class, called AlarmClock, that keeps track of the clock ticks for the application writer.

Building your first prevalent system
Now to build your first prevalent system -- download Prevayler from the Prevayler Web site. Make sure that you install it correctly by putting it in your CLASSPATH.

Starting up
The Prevayler system is a very simple command-line based login manager. You need to track the user login ID, password, name, and e-mail address. Start by defining the User class (see Listing 1):


package org.prevayler.intro.users;
import java.io.Serializable;

/** Represents a system's user.
 * @author Carlos Villela
 */
public class User implements Serializable {

  private String name = "";
  private String email = "";
  private String login = "";
  private String password = "";

  // getters and setters go here
  // ...
}

This very straightforward property-only class is the only business object in this simple demonstration of what object prevalence and Prevayler can do. In real-world applications, you would define many more business objects, as your design evolves.

Now, link a HashMap of those Users into the PrevalentSystem as shown in Listing 2:


package org.prevayler.intro.users;
import java.util.HashMap;

import org.prevayler.implementation.AbstractPrevalentSystem;

/** Prevalent system for the Users Manager Demo.
 * @author Carlos Villela
 */
public class UserLogonSystem extends AbstractPrevalentSystem {

  /** 
   * Map to hold all user references on the system.
   */
  private HashMap users;

  public UserLogonSystem() {
    this.users = new HashMap();
  }

  public HashMap getUsers() {
    return users;
  }

  public void setUsers(HashMap users) {
    this.users = users;
  }
} 

You have now defined your system using the AbstractPrevalentSystem class. You could have implemented the PrevalentSystem interface directly if you needed to use the system clock or if your system needed to extend some other class.

Creating commands
Before you can do anything with this system,you need to modify and query its data. As shown in Listing 3, start with the AddUser command:


package org.prevayler.intro.users.commands;
import java.io.Serializable;
import java.util.HashMap;

import org.prevayler.Command;
import org.prevayler.PrevalentSystem;
import org.prevayler.intro.users.User;
import org.prevayler.intro.users.UserManager;

/** Adds a user to a UserManager
 * @author Carlos Villela
 */
public final class AddUser implements Command {

  private User user;

  /**
   * @see Command#execute(PrevalentSystem)
   */
  public Serializable execute(PrevalentSystem system) throws 
      InvalidUserException {

    if (user == null)
      throw new InvalidUserException("null user");

    if (user.getLogin() == null)
      throw new InvalidUserException("null login");

    if (user.getPassword() == null)
      throw new InvalidUserException("null password");

    if (user.getLogin().length() < 1)
      throw new InvalidUserException("invalid login size");

    if (user.getPassword().length() < 6)
      throw new InvalidUserException("invalid password size");

    UserManager users = (UserManager) system;
    HashMap usersMap = users.getUsers();

    assert usersMap != null;

    if (usersMap.containsKey(user.getLogin()))
      throw new InvalidUserException("login already exists");

    usersMap.put(user.getLogin(), user);

    return this;
  }

  // getters and setters for user go here
}

Take a closer look at this code. AddUser Command is an equivalent to an SQL INSERT -- using the HashMap.put method is the same as inserting data into an indexed relational table. You can create more complex commands and that is what comes next.

Listing 4 shows the source for a more complex command, ChangeUser:


package org.prevayler.intro.users.commands;
import java.io.Serializable;
import java.util.HashMap;

import org.prevayler.Command;
import org.prevayler.PrevalentSystem;
import org.prevayler.intro.users.User;
import org.prevayler.intro.users.UserManager;

public final class ChangeUser implements Command {

  private User user;

  public Serializable execute(PrevalentSystem system) throws 
      InvalidUserException {

    if (user == null)
      throw new InvalidUserException("null user");
    if (user.getLogin() == null)
      throw new InvalidUserException("null login");

    if (user.getPassword() == null)
      throw new InvalidUserException("null password");

    if (user.getLogin().length() < 1)
      throw new InvalidUserException("invalid login size");

    if (user.getPassword().length() < 6)
      throw new InvalidUserException("invalid password size");

    UserManager users = (UserManager) system;
    HashMap usersMap = users.getUsers();

    if (!usersMap.containsKey(user.getLogin()))
      throw new InvalidUserException("non-existent login");

    usersMap.remove(user.getLogin());
    usersMap.put(user.getLogin(), user);

    return this;
  }

  // getters and setters for user go here
}

The ChangeUser Command introduces subtle changes: when I want to change a User, I first check for its existence in the system. This command is, in practice, a SELECT and an UPDATE. Because object-oriented programmers do not want to spend time thinking about rolling back a transaction, I check every possible thing that can go wrong. I can do this because all the data are available and all validation is performed before actually changing anything. The transaction is now a method -- its execution can run to completion or throw an Exception.

You can write rollback-free programming in other ways but I do not cover more examples on this occasion.

These two commands can potentially make you think that you are using SQL. You are not limited, by any means, to do just that. Commands can be much, much smarter. You can think of them as agents in your system. They can start doing work even before you execute() them as they validate data and state, give warnings to your application, and much more. Your data manipulation is freed from the usual SQL and other query language limitations.

Putting it all together
Next, I create the Main class, which provides the command-line interface. I can create any kind of user interaction I want, including Applets, Swing GUIs, Servlets or anything else that can be used to create new commands and apply them on the system.

Listing 5 shows the code that creates the command-line interface:


/**
 * Main class for the Users Manager Demo.
 */
public class Main {

  public static void usage() {
  System.out.println(
    "Usage: Main <list|add|change|del> login <parameters>\n\n"
    + "Parameters:\n"
    + "    list: none\n"
    + "    add: <login> <password> <name> <email>\n"
    + "    change: <login> <password> <name> <email>\n"
    + "    del: <login>\n");
  System.exit(0);
  }

  public static void main(String[] args) {
  try {
    SnapshotPrevayler prevayler = 
    new SnapshotPrevayler(new UserManager());

    if (args.length < 1) {
    usage();
    } else if (args[0].equalsIgnoreCase("list")) {
    listUsers(prevayler);

    } else if (args[0].equalsIgnoreCase("add") & args.length >= 5) {
    addUser(prevayler, args[1], args[2], args[3], args[4]);

    } else if (args[0].equalsIgnoreCase("del") & args.length >= 2) {
    deleteUser(prevayler, args[1]);

    } else if (args[0].equalsIgnoreCase("change")) {
    changeUser(prevayler, args[1], args[2], args[3], args[4]);

    } else {
    usage();
    }

  } catch (Exception e) {
      e.printStackTrace();
  }
  }
  
  // client methods go here...
}

Listing 6 and Listing 7 show the addUser and changeUser methods, respectively. For brevity, I have omitted the listings for deleteUser and listUsers, although they are included in the demo code (see Resources). You will notice how they are very simple client methods.


  /** Adds a new user to the system.
   * @param prevayler the where we will execute the command
   * @param login user's login name
   * @param password user's password
   * @param name user's name
   * @param email user's e-mail address
   */
  private static void addUser(Prevayler prevayler, String login, 
      String password, String name, String email)
  throws Exception {
    System.out.println("Adding user '" + login + "'");
    User u = new User();
    u.setLogin(login);
    u.setPassword(password);
    u.setName(name);
    u.setEmail(email);

    AddUser cmd = new AddUser();
    cmd.setUser(u);
    try {
      prevayler.executeCommand(cmd);
    } catch (InvalidUserException e) {
      System.out.println("Error: " + e.getMessage());
    }
  }

  /** Changes the user's data.
   * @param prevayler the where we will execute the command
   * @param login user's login name to change data
   * @param password user's new password
   * @param name user's new name
   * @param email user's new e-mail address
   */
  private static void changeUser(Prevayler prevayler, String login, 
      String password, String name, String address)
    throws Exception {
    System.out.println("Changing user '" + login + "'");

    User u = (User) 
        ((UserManager) prevayler.system()).getUsers().get(login);

    u.setPassword(password);
    u.setName(name);
    u.setEmail(address);

    ChangeUser cmd = new ChangeUser();
    cmd.setUser(u);
    prevayler.executeCommand(cmd);
  }

Prevalent systems and the Web
With Prevayler, you can develop many kinds of applications. Its simplicity and ease of use makes it ideal for quickly prototyping Web applications. You can then put these applications into production with very little effort. As they are simple Java language classes and beans, you can use your favorite IDE to create the beans and their relationships very quickly. One of the Web applications that I developed with Prevayler was prototyped very quickly. It performed so well that the customer decided to put the system into immediate production without even considering use of a database system. The deployment of the application was also very simple as there were no need to set up database servers. I did change the data backup policies a little and put the PrevalenceBase directory (where Prevayler stores the serialized Commands) under backup control. Figure 2 shows how a Web application can benefit from object prevalence.

Figure 2. Building a prevalent Web application
Building a prevalent Web application

The architecture in Figure 2 has one problem. With only one set of Business Objects is in each Web Container, the architecture does not support clustering or replication. One possible solution to this problem, as Prevayler is implemented today, is to add a shared command-log storage (like an NFS share or Windows shared drive). Then, all Web containers can read the same command log and snapshots and elect one of the machines as the hot system. With a shared command log, the system can have a replica of the business objects on another virtual machine. The replica can also read all commands applied to the hot system and apply them in the exact same order. At backup time, the replica stops reading the commands and its snapshot is safely taken. Then, the replica resumes reading the command queue and gets back into synchronization with the hot system. The replication approach just described was not needed on the customer prototype application that I mentioned earlier, as the backup time was synchronized with the snapshot time of the prevalent system. This way, only one file was backed up, instead of the full command log. Figure 3 graphically illustrates replication.

Figure 3. Replication inside a prevalent Web application
Replication inside a prevalent Web application

The Prevayler team is working hard to create a better replication system, for those who really need it. After working on a number of applications, I was most surprised to find that the most common bottleneck in a Prevalent system is not the data access and manipulation logic, but rather the Web server.

Conclusion
Object prevalence is a very useful concept, which can be used for many kinds of systems. Its simplicity is one of its major strengths. But, simplicity sometimes frightens people. People often ask me questions:

  • Is it robust?
  • Is it scalable?
  • What is performance like?

The best way to answer these questions is to run the ScalabilityTest in the org.prevayler.test.scalability package and see for yourself. With this test, you can see the power of the object prevalence concept.

Remember, Prevayler is open source and object prevalence is a concept, not a tool. You can implement it in other languages (as others have already done with SmallTalk and Ruby). If you do, please drop the Prevayler team a note. We will be glad to support you!

Resources

About the author
Photo of Carlos Villela
Carlos Eduardo Villela is a 19-year old Brazilian graduate in Information Systems. Programming since he was very young, almost 8 years experience has made him a Java and Python enthusiast. He currently runs his own consulting company, Stage2 Sistemas Web. He is the maintainer and editor of the Prevayler Web site and has successfully used Prevayler in a number of projects. You can reach him at carlos@stage2.com.br.

원본출처 : http://www-106.ibm.com/developerworks/library/wa-objprev/

[Top]
No.
제목
작성자
작성일
조회
1282Oracle 10g vs PostgreSQL 8 vs MySQL 5
정재익
2006-12-16
13124
949Future Directions in DBMS Research
정재익
2004-03-25
14656
873An introduction to object prevalence
정재익
2003-10-30
20956
86325 가지 SQL 작성법
정재익
2003-10-22
21331
862정규화와 Database 모델링
정재익
2003-10-22
23139
778애플리케이션 중심의 튜닝 최적화 유도 (2)
정재익
2003-06-19
10079
Valid XHTML 1.0!
All about the DATABASE... Copyleft 1999-2023 DSN, All rights reserved.
작업시간: 0.048초, 이곳 서비스는
	PostgreSQL v16.1로 자료를 관리합니다