A RAD architecture is one that upholds timeliness of delivery as a central overarching principle. A RAD architecture both meets the needs of your customers and expedites the delivery of the system. In short, it is about defining an architecture that is implemented easily and efficiently by the development team and hence completed in a timeframe acceptable to the customer.
The following are some options for achieving this goal.
Work to the Strengths of the Team
Contrary to what many project managers would like to believe, software engineers are not faceless drones who spend all their spare time reading technical reference material on Java. The members of any given project team have acquired a diverse and varied range of skills and knowledge during their time in the software development industry. Knowing the strengths of the team is an important consideration when designing the system: the architecture should take advantage of these strengths.
Some people may find this a contentious statement, arguing that architecture must be based on the technology that best fits the system requirements. This is a valid argument. If the choice of technology was always based on the knowledge of the team, most IT applications would still be implemented in COBOL. Nevertheless, the team's collective technical background should be taken into consideration when time is a critical factor for the project.
The same rule applies when looking to the job market for skills for the project. If members of the team leave, or a new project team has to be formed, filling vacant job roles could prove problematic if skills in an uncommon technology are required.
As an example, consider the choice of which scripting language to adopt on a new project. Both Jython and Groovy are excellent choices as scripting languages. Both are each compatible with Java and offer highly expressive language constructs. Nevertheless, if the team has a large base of experience with Perl, then neither Jython nor Groovy may be the best option. Where possible, you may wish to stick to what the team knows well and work to your strengths. Without doubt, there is value in learning new technologiesjust be cautious of learning on time-critical projects.
Chapter 9 looks at Jython and Groovy.
Use Best-of-Breed Frameworks
J2EE is all about frameworks, yet extensive as the J2EE platform is, it does not cater to every scenario. Enter the application framework to plug the gaps.
A suitable software framework can significantly reduce the amount of code we need to write and improve the quality of the design. Thanks to the groundswell of support for all things Java, J2EE developers are spoiled for choice when it comes to the availability of software frameworks.
Frameworks are available for just about every conceivable application, including Web development, security, build environments, test environments, and GUI development. Table 4-1 lists a selection of the popular offerings from the Java community and highlights the diversity of the frameworks available.
The choice of which framework to use on a project is an important decision that can be critical to the success of the project. The following guidelines should help you through the decision-making process:
Picking the best framework for your project reduces the development timeframe and should contribute to the quality of the final application. Here are some metrics to use as a guide when assessing a framework in terms of its maturity and longevity:
Choosing a framework is an important decision, so be sure to do your homework before making it.
By thinking ahead during the design process, you can make the lives of developers and testers alike a lot easier. The need to think ahead is especially relevant in the area of testing.
Sadly, testing is often unfairly seen as the poor relation to development. This perception is unwarranted, as testing is an integral part of the software development lifecycle. As a rough rule of thumb, testing hours on a project will likely equal that of developmentany less, and the system is probably undertested.
Any actions you can take as an architect to make testing easier will not only result in a higher quality system, but also reduce the total testing effort. Exactly how the architect can design with testing in mind is largely dependent on the application.
Testing is a key part of RAD and is covered in some detail in Chapters 14 and 15.
Outlined below are some points for consideration when working on the system design.
Include Test Stubs and Unit Tests as Part of the Design
Considerable effort is often wasted on projects when developers produce their own ad hoc testing harnesses. These test harnesses sprout like weeds throughout the project's code base. They burst into existence as developers find themselves unable to test new components or modules due to a dependency on a piece of software that hasn't yet been written. The solution is often to quickly implement a test stub in place of the missing software. Unfortunately, this task consumes time, and on a large project, this practice can quickly get out of hand with engineers duplicating the same test stubs.
These test stubs seldom find their way into source control and are often of dubious quality because no time has been set aside to the developer for this task on the project plan. To avoid this problem, the architect must think strategically and incorporate all necessary test stubs into the design. The designer must work in conjunction with the project manager to ensure software components are built in a logical sequence to avoid the need for ad hoc test stubs. Where this is not possible, the test stub should be included as part of the object model and time set aside on the plan for the development of the stub.
With this approach, all test harnesses and stubs become part of the project's build cycle and are available to the entire team. They can also be made available to the test team for early testing of delivered components. The test team in turn can give feedback on the quality of the test stubs.
Therefore, the rule is, think about your testing needs early on, and design testing into the system. Your developers and your testers will thank you for it.
Be Wary of Weakly Typed Interfaces
Due to the surge in popularity of XML as a format for data transfer, the interfaces on distributed components the world over are taking on a very similar appearance. Thus, many methods now have the signature
void update(String document);
The parameter document in this case carries an XML document as the payload. Using XML in this way has largely taken over from the conventional approach of defining each argument as a typed parameter on the method as it provides an effective approach for ensuring loose coupling between components. The structure of the XML document can be modified without the need to change the interface, allowing for a design that is very resilient to change.
The downside to this approach is that the compiler cannot perform any type checking of the data on our behalf. Type checking, or validation of the document against a schema, must now take place at runtime. With XML, Java becomes a type-less language.
This presents the test team with something of a headache. Errors are harder to catch at runtime than at compile time, and the testing effort must now be exhaustive.
If you intend to use XML in this manner to achieve a highly decoupled system, then ensure the necessary safeguards are in place to replace the compiler's type safety. Where practical, validate against an XML schema and always log any errors caught at runtime in a manner that makes them immediately apparent.
Be Wary of Designing for Reuse
Software reuse is a key element of RAD, but reuse is a double-edged sword. Making the reuse of software components part of your strategy for rapid development is not an easy task, even with the benefits of Java as an OOP language and J2EE as a container for housing prefabricated components.
Designing code for reuse is a difficult and time-consuming task. The developer must consider all reasonable future scenarios for a component and accommodate them accordingly within the software architecture. This adds excessive complexity to the component's design and implementation. On a project with a tight timeframe, you should avoid overengineering the architecture for future reuse purposes. Rapid development project teams should design only to have the software meet the current known requirements, not possible future ones.
This fits in with the fundamental XP principle of keeping the design simplea principle aptly described by the XP expression, "You ain't gonna need it," or YAGNI. Remember, less code means fewer defects.
Chapter 3 covers the practices of XP.
Rapid development requires designing with reuse as opposed to designing for reuse.
Designing for reuse adds considerable complexity to the project and hence consumes additional resources and time. Instead, develop reusable components as a separate research and development project aimed at building your company's adaptive development foundation.
Have the components built, tested, and documented before rapid development project teams use them. A suite of suitably designed prefabricated components provides a development company with a competitive advantage, but this is an investment that needs to be made as part of a company's preparation effort for rapid development. Don't build the components of your adaptive foundation on time-critical customer projects.
Apply Orthogonal Design
Orthogonality is a term taken from geometry that applies to two lines that intersect at right angles. As vectors, these lines are said to be orthogonal and therefore independent. Thus, move along one of the lines, and your position projected on the other line remains unchanged.
Despite its origins in geometry, orthogonality is becoming a watchword in development circles after being popularized as a software engineering concept by Andrew Hunt and David Thomas in their book The Pragmatic Programmer [Hunt, 1999].
For software engineering, the term neatly summarizes those elements of design that are synonymous with well-constructed software; that is, they exhibit the admirable characteristics of loose coupling and high cohesion.
Orthogonal components are independent, self-contained, and have a clearly stated responsibility. Changes to the internals of an orthogonal component do not impact other components within the system.
This independence between components is vital to rapid development. A loosely coupled system can more easily accommodate change than one that is tightly coupled. A change in the database should not require a change in the code responsible for the application's user interface, and vice versa.
An orthogonal design safeguards against change, and a design that can absorb the impact of change supports rapid development. Moreover, components that exhibit orthogonality are easier to code, test, and maintain due to their self-contained nature. Again, these are all attributes that help make a team productive and greatly benefit a RAD project.
Object-oriented development practices, if applied correctly, result in orthogonal designs. A staple of object-oriented design is the use of interfaces. By designing our components around interfaces, we can use interfaces as shields against implementation changes.
One successful approach for achieving an orthogonal design is to base your design upon the use of layers.
Adopt a Layered Architecture
In software engineering, separating the application code into layers is good design practice and enables the construction of robust applications. In the layered approach, each layer provides its services to the layer immediately above through a series of well-defined interfaces.
Each layer should rely only on the services of the layer immediately beneath it. This ensures higher layers in the design hierarchy are insulated from changes in the lower layers.
Here are the layers commonly found in business software:
Layers are abstract and represent a logical boundary within the software, not a physical one. Distributed computing makes it possible to achieve a physical separation of a system's logical layers into tiers. The tiers of a multitier architecture are physically remote from one another, typically located on different servers or executing in a separate process. This physical division of the layers into tiers gives rise to a multitier architecture.
The architecture of a J2EE application comprises three tiers.
The client tier is the "final front-tier." It should contain code specific only to the user interface and rely upon the middle tier for all business knowledge. J2EE clients run on anything from desktops to handheld devices and access the middle tier remotely, using an appropriate network infrastructure such as a LAN or via the Internet.
On the J2EE platform, this middle tier is the J2EE server, which provides services for addressing the concerns of both Web clients and application business logic. The middle tier of the J2EE platform divides into two tiers for each of these concerns:
The J2EE server manages access to resources required by the business components. These resources reside within the enterprise information system (EIS) tier.
Enterprise Information System Tier
The EIS tier, or back-end tier, is the lowermost tier in J2EE and houses the enterprise resources upon which a J2EE application relies. Examples of enterprise resources include relational databases and existing legacy systems. The resources of the EIS tier do not fall under the control of the J2EE server. Instead, the J2EE server offers a set of standard services for integrating with EIS tier resources. These services include
The J2EE server undertakes the responsibility of managing and pooling connections to the various resources of the EIS tier as well as the management of transactions that span enterprise resources.
The J2EE platform makes possible the design of orthogonal systems using multitier architectures. The next section considers the strengths and weaknesses of using multiple tiers and explores some design options for J2EE architectures.