Important Announcement
PubHTML5 Scheduled Server Maintenance on (GMT) Sunday, June 26th, 2:00 am - 8:00 am.
PubHTML5 site will be inoperative during the times indicated!

Home Explore Applying UML and Patterns

Applying UML and Patterns

Published by soontarin, 2018-02-26 22:16:41

Description: applying-uml-and-patterns

Keywords: UML,OOAD,System Design

Search

Read the Text Version

34 - DESIGNING A PERSISTENCE FRAMEWORK WITH PATTERNSNow, IMapper, AbstractPersistenceMapper, and AbstractRDBMapper are part of the framework.The application programmer needs only to add his or her subclass, such asProductSpecificationRDBMapper, and ensure it is created with the table name (to pass viaconstructor chaining up to the AbstractRDBMapper).The Database Mapper class hierarchy is an essential part of the framework; new subclassesmay be added by the application programmer to customize it for new kinds of persistent storagemechanisms or for new particular tables or files within an existing storage mechanism. Figure34.10 shows some of the package and class structure. Notice that the NextGen-specific classesdo not belong in the general technical services Persistence package. I think this diagram, com-bined with Figure 34.9, illustrates the value of a visual language like the UML to describe partsof software; this succinctly conveys much information. NextGen Persistence ProductSpecification FileWithXMLMapper ProductSpecification # getObjectFromStorage(OID) : Object RDBMapper ProductSpecification + ProductSpecificationRDBMapper(tableName) InMemoryTestDataMapper # getObjectFromRecord(OID, DBRecord) : Object # getObjectFromStorage(OID) : Object Sale RDBMapper ... # getObjectFromRecord(OID, DBRecord) : Object Persistence 1 + PersistenceFacade «interface» IMapper getInstance() : PersistenceFacade Class 1 get( OID, Class ) : Object get(OID) : Object put( OID, Object ) put( OID, Object ) ... ... Abstract Abstract RDBMapper PersistenceMapper + AbstractRDBMapper(tableName) + get( OID) : Object {leaf} # getObjectFromStorage(OID) : Object {leaf} # getObjectFromStorage(OID) : Object # getObjectFromRecord(OID, DBRecord) : Object ...Fig-ugret3D4B.R10ecTohrde(OpeIDrs)i:stDenBcReefcroarmd ework.550

MATERIALIZATION WITH THE TEMPLATE METHOD PATTERN Notice the class ProductSpecificationlnMemoryTestDataMapper. Such classes can be used to serve up hard-coded objects for testing, without accessing any external persistent store.The UP and the Software Architecture Document In terms of the UP and documentation, recall that the SAD is a learning aid for future developers, which contains architectural views of key noteworthy ideas. Including diagrams such as Figure 34.9 and Figure 34.10 in the SAD for the NextGen project is very much in the spirit of the kind of information an SAD should contain.Synchronized or Guarded Methods in the UML The AbstractPersistenceMapper—get method contains critical section code that is not thread safe—the same object could be being materialized concurrently on different threads. As a technical service subsystem, the persistence service needs to be designed with thread safety in mind. Indeed, the entire subsystem may be distributed to a separate process on another computer, with the PersistenceFacade transformed into a remote server object, and with many threads simultaneously running in the subsystem, serving multiple clients. The method should therefore have thread concurrency control—if using Java, add the synchronized keyword. Figure 34.11 illustrates a synchronized method in a class diagram.{ {guarded} means a \"synchronized\" method; that is,// Javapublic final synchronized Object get( OID oid ) only 1 thread may execute at a time within the{ ... } family of guarded methods of this object.} Abstract PersistenceMapper IMapper + get( OID) : Object {leaf, guarded} ...Figure 34.11 Guarded methods in the UML. 551

34 - DESIGNING A PERSISTENCE FRAMEWORK WITH PATTERNS34.13 Configuring Mappers with a MapperFactory Similar to previous examples of factories in the case study, the configuration of the PersistenceFacade with a set of IMapper objects can be achieved with a factory object, MapperFactory. However, as a slight twist, it is desirable to not name each mapper with a different operation. For example, this is not desirable: class MapperFactory public IMapper getProductSpecificationMapper() {...} public IMapper getSaleMapper() {...} This does not support Protected Variations with respect to a growing list of mappers—and it will grow. Consequently, the following is preferred: class MapperFactory public Map getAllMappers( ) {...} } where the java. util.Map (probably implemented with a HashMap) keys are the Class objects (the persistent types), and the IMappers are the values. Then, the facade can initialize its collection of IMappers as follows: class PersistenceFacade private java.util.Map mappers = MapperFactory.getlnstance( ).getAllMappers( ); The factory can assign a set of IMappers using a data-driven design. That is, the factory can read system properties to discover which IMapper classes to instantiate. If a language with reflective programming capabilities is used, such as Java, then the instantiation can be based on reading in the class names as strings, and using something like a Class.newlnstance operation for instantiation. Thus, the mapper set can be reconfigured without changing the source code.34.14 Pattern: Cache Management It is desirable to maintain materialized objects in a local cache to improve performance (materialization is relatively slow) and support transaction management operations such as a commit. The Cache Management pattern [BW96] proposes making the Database Mappers responsible for maintaining its cache. If a different mapper is used for each class of persistent object, each mapper can maintain its own cache.

552 consolidating and hiding SQL statements in one class When objects are materialized, they are placed in the cache, with their OID as the key. Subsequent requests to the mapper for an object will cause the mapper to first search the cache, thus avoiding unnecessary materialization.34.15 Consolidating and Hiding SQL Statements in One Class Hard-coding SQL statements into different RDB mapper classes is not a terrible sin, but it can be improved upon. Suppose instead: • There is a single Pure Fabrication class (and it's a singleton) RDBOpera- tlons where all SQL operations (SELECT, INSERT, ...) are consolidated. • The RDB mapper classes collaborate with it to obtain a DB record or record set (for example, ResultSet). • Its interface looks something like this: class RDBOperations { public ResultSet getProductSpecificationData( OID oid ) {...} public ResultSet getSaleData( OID oid ) {...} } So that, for example, a mapper has code like this: class ProductSpecificationRDBMapper extends AbstractPersistenceMapper { protected Object getObjectFromStorage( OID oid ) { ResultSet rs = RDBOperations.getlnstance( ).getProductSpecificationData( oid ); ProductSpecification ps = new ProductSpecification O; ps.setPrice( rs.getDouble( \"PRICE\" ) ); ps.setOID( oid ); return ps; The following benefits accrue from this Pure Fabrication: • Ease of maintenance and performance tuning by an expert. SQL optimiza tion requires a SQL aficionado, rather than an object programmer. With all the SQL embedded in this one class, it is easy for the SQL expert to find and work on it. • Encapsulation of the access method and details. For example, hard-coded SQL could be replaced by a call to a stored procedure in the RDB in order to obtain the data. Or a more sophisticated metadata-based approach to gen erating the SQL could be inserted, in which SQL is dynamically generated from a metadata schema description read from an external source.

553 34 - DESIGNING A PERSISTENCE FRAMEWORK WITH PATTERNS As an architect, the interesting aspect of this design decision is that it is influenced by developer skills. A trade-off between high cohesion and convenience for a specialist was made. Not all design decisions are motivated by \"pure\" software engineering concerns such as coupling and cohesion.34.16 Transactional States and the State Pattern Transactional support issues can get complex, but to keep things simple for the present—to focus on the GoF State pattern—assume the following: • Persistent objects can be inserted, deleted, or modified. • Operating on a persistent object (ofr example, modifying it) does not cause an immediate database update; rather, an explicit commit operation must be performed. In addition, the response to an operation depends on the transactional state of the object. As an example, responses may be as shown in the statechart of Figure 34.12. State chart: PersistentObject [new (not from DB)] [ from DB] OldClean New commit / insert save OldDirty rollback / reload commit / update Legend: delete delete New--newly created; not in DB rollback / reload OldDelete Old--retrieved from DB Clean--unmodified commit / delete Dirty--modified Deleted Figure 34.12 Statechart for PersistentObject. For example, an \"old dirty\" object is one retrieved from the database and then modified. On a commit operation, it should be updated to the database—in contrast to one in the \"old clean\" state, which should do nothing (because it hasn't changed). Within the object-oriented PFW, when a delete or save operation is performed, it does not immediately cause a database delete or save; rather, the persistent object transitions to the appropriate state, awaiting a commit or rollback to really do something. As a UML comment, this is a good example of where a statechart is helpful in succinctly communicating information that is otherwise awkward to express.554

TRANSACTIONAL STATES AND THE STATE PATTERNIn this design, assume that we will make all persistent object classes extend aPersistentObject class,6 that provides common technical services for persistence.7 Forexample, see Figure 34.13. Domain ProductSpecification Persistence... PersistentObject oid : OID timeStamp: DateTime commit() delete() rollback() save() ...Figure 34.13 PersistentObjects.Now—and this is the issue that will be resolved with the State pattern—notice thatcommit and rollback methods require similar structures of case logic, based on atransactional state code, commit and rollback perform different actions in their cases,but they have similar logic structures.An alternative to this repeating case logic structure is the GoF State pattern.6. Ambler00b] is a good reference on a PersistentObject class and persistence layers, although the idea is older.7. Some issues with extending a PersistentObject class are discussed later. Whenever a domain object class extends a technical services class, it should be pause for reflection, as it mixes architectural concerns (persistence and application logic). 555

34 - DESIGNING A PERSISTENCE FRAMEWORK WITH PATTERNS State Context / Problem An object's behavior is dependent on its state, and its methods contain case logic reflecting conditional state-dependent actions. Is there an alternative to conditional logic? Solution Create state classes for each state, implementing a common interface. Delegate state-dependent operations from the context object to its current state object. Ensure the context object always points to a state object reflecting its current state. Figure 34.14 illustrates its application in the persistence subsystem. State-dependent methods in PersistentObject delegate their execution to an associated state object. If the context object is referencing the OldDirtyState, then 1) the commit method will cause a database update, and 2) the context object will be reassigned to reference the OldCleanState. On the other hand, if the context object is referencing the OldCleanState, the inherited do-nothing commit method executes and does nothing (as to be expected, since the object is clean). Observe in Figure 34.14 that the state classes and their behavior correspond to the state chart of Figure 34.12. The State pattern is one mechanism to implement a state transition model in software.8 It causes an object to transition to different states in response to events. As a performance comment, these state objects are—ironically—stateless (no attributes). Thus, there does not need to be multiple instances of a class—each is a singleton. Thousands of persistent objects can reference the same OldDirtyState instance, for example.34.17 Designing a Transaction with the Command Pattern The last section took a simplified view of transactions. This section extends the discussion, but does not cover all transaction design issues. Informally, a transaction is a unit of work—a set of tasks—whose tasks must all complete successfully, or none must be completed. That is, its completion is atomic. 8. There are others, including hard-coded conditional logic, state machine interpreters, and code generators driven by state tables. 556

DESIGNINGATRANSACTIONWITHTHECOMMANDPATTERN { state.delete( this ) }{ state.rollback( this ) } { state.commit( this ) } PersistentObject { state.save( this ) } PObjectStateoid : OIDstate : PObjectState { // default no-opcommit() * commit(obj : PersistentObject) // bodies fordelete() 1 delete(obj : PersistentObject) // each methodrollback()save() rollback(obj : PersistentObject) }setState(PObjectState) save(obj : PersistentObject)... OldDirty OldClean New OldDelete State State State State Product Sale commit(...) delete(...) commit(...) commit(...) Specification ... delete(...) save(...) rollback(...) ... rollback(...)......{ // commitPersistenceFacade.getInstance().update( obj )obj.setState( OldCleanState.getInstance() ) }{ // rollback { // commitPersistenceFacade.getInstance().reload( obj ) PersistenceFacade.getInstance().insert( obj )obj.setState( OldCleanState.getInstance() ) } obj.setState( OldCleanState.getInstance() ) }{ // delete { // commitobj.setState( OldDeleteState.getInstance() ) } PersistenceFacade.getInstance().delete( obj ) obj.setState( DeletedState.getInstance() ) }{ // saveobj.setState( OldDirtyState.getInstance() ) } Figure 34.14 Applying the State pattern.9 In terms of the persistence service, the tasks of a transaction include inserting, updating, and deleting objects. One transaction could contain two inserts, one update, and three deletes, for example. To represent this, a Transaction class is added [Ambler00b].10 As pointed out in [Fowler0l], the order of database tasks within a transaction can influence its success (and performance). 9. The Deleted class is omitted due to space constraints in the diagram. l0.This is called a UnitOfWork in [Fowler0l]. 557

34 - DESIGNINGAPERSISTENCEFRAMEWORKWITHPATTERNS For example: 1. Suppose the database has a referential integrity constraint such that when a record is updated in TableA that contains a foreign key to a record in TableB, the database requires that the record in TableB already exists. 2. Suppose a transaction contains an INSERT task to add the TableB record, and an UPDATE task to update the TableA record. If the UPDATE executes before the INSERT, a referential integrity error is raised. Ordering the database tasks can help. Some ordering issues are schema-specific, but a general strategy is to first do inserts, then updates, and then deletes. Mind that the order in which tasks are added to a transaction by an application may not reflect their best execution order. The tasks need to be sorted just before their execution. This leads to another GoF pattern: Command. Command Context / Problem How to handle requests or tasks that need functions such as sorting (prioritizing), queueing, delaying, logging, or undoing? Solution Make each task a class that implements a common interface. This is a simple pattern with many useful applications; actions become objects, and thus can be sorted, logged, queued, and so forth. For example, in the PFW, Figure 34.15 shows Command (or task) classes for the database operations. There is much more to completing a transaction solution, but the key idea of this section is to represent each task or action in the transaction as an object with a polymorphic execute method; this opens up a world of flexibility by treating the request as an object itself. The quintessential example of Command is for GUI actions, such as cut and paste. For example, the CutCommand's execute method does a cut, and its undo method reverses the cut. The CutCommand will also retain the data necessary to perform the undo. All the GUI commands can be kept in a history stack, so that they can be popped in turn, and each undone.Another common use of Command is for server-side request handling. When a server object receives a (remote) message, it creates a Command object for that request, and hands it off to a CommandProcesser [BMRSS96], which can queue, log, prioritize, and execute the commands.558

LAZY MATERIALIZATION WITH A VIRTUAL PROXY Transaction { sort() for each ICommand cmd cmd.execute() }commands : List 1 1..* «interface» undo is a no-op for ICommand this example, but acommit() more complexaddDelete(obj:PersistentObject) execute( ) solution adds aaddInsert( obj:PersistentObject) undo() polymorphic undoaddUpdate( obj:PersistentObject) to each subclasssort() which uniquely... knows how to undo an operationuse SortStrategy objects to allow DBCommand PersistentObjectdifferent sort algorithms to order theCommands object : PersistentObject 1 commit() 1 ...{ execute() {abstract}commands.add( new DBUpdateCommand(obj) ); undo() {leaf}}perhaps simply DBUpdateCommand DBInsertCommand DBDeleteCommand object.commit() execute() execute() execute()but each Command canperform its own uniqueactions Figure 34.15 Commands for database operations.34.18 Lazy Materialization with a Virtual Proxy It is sometimes desirable to defer the materialization of an object until it is absolutely required, usually for performance reasons. For example, suppose that ProductSpecification objects reference a Manufacturer object, but only very rarely does it need to be materialized from the database. Only rare scenarios cause a request for manufacturer information, such as manufacturer rebate scenarios in which the company name and address are required. The deferred materialization of \"children\" objects is known as lazy materialization. Lazy materialization can be implemented using the Virtual Proxy GoF pattern—one of many variations of Proxy. A Virtual Proxy is a proxy for another object (the real subject) that materializes the real subject when it is first referenced; therefore, it implements lazy materialization. It is a lightweight object that stands for a \"real\" object that may or may not be materialized. 559

34 - DESIGNING A PERSISTENCEFRAMEWORKWITHPATTERNS A concrete example of the Virtual Proxy pattern with ProductSpecification and Manufacturer is shown in Figure 34.16. This design is based on the assumption that proxies know the OID of their real subject, and when materialization is required, the OID is used to help identify and retrieve the real subject. Note that the ProductSpecification has attribute visibility to an IManufacturer instance. The Manufacturer for this ProductSpecification may not yet be materi- alized in memory. When the ProductSpecification sends a getAddress message to the ManufacturerProxy (as though it were the materialized manufacturer object), the proxy materializes the real Manufacturer, using the OID of the Man- ufacturer to retrieve and materialize it.actually references an PersistentObjectinstance of oidManufacturerProxy ... ProductSpecification 1 玦nterface 1 IManufacturer manufacturer : IManufacturer ... getAddress() ... getManufacturerAddress() : Address{ Manufacturer * Manufacturerreturn manufacturer.getAddress() Proxy Proxy-for 1 address}1 realSubject : IManufacturer - getRealSubject() : IManufacturer realSubject getAddress() ... + getAddress() ...{ { return getRealSubject().getAddress()if ( realSubject == null ) } realSubject = PersistenceFacade.get(oid, Manufacturer.class);return realSubject; 3 2} Figure 34.16 Manufacturer Virtual Proxy.560


Like this book? You can publish your book online for free in a few minutes!
Create your own flipbook