The Redbook has a several links to pages jam packed with information but… woah… those pages don’t exist anymore.
But don’t worry – using the good old WAYBACK ARCHIVE, I’ve managed to recover the essence of that article. Sadly, most of the images have been lost into the cobwebs of time, but the core tenets of modernization remains.
Pattern Recognition: Ease Modern RPG Programming not found
You’ve taken an ILE RPG course or read a book or an article. You’ve learned what procedures, service programs, binding directories, and activation groups are. With all these tools ready, you sit down to write your first ILE application . . . and nothing fits together! “It’s just too complicated!” you exclaim in frustration. “How do I do file access? How do I handle errors when the routines are in different places? Where do I split my programs into procedures?”
If this sounds like you, you might be relieved to know that you’re not alone in your confusion. There’s more to learning ILE than understanding the concepts. There’s also a new design pattern to learn. Whether you realize it or not, if you’ve been a programmer for many years, you’ve developed your own design patterns. You’re good at programming with these patterns because you’ve used them for years. Anytime you switch from one programming paradigm to another, you have to learn new patterns.
When you’ve developed strong new design patterns in ILE, you’ll become as proficient with it and be able to leverage the reusability and maintainability that make ILE programs more valuable to your company (or client) than older methods.
A Pattern I Unlearned
Ordinarily, when you read about patterns in a book or article about computer design theory, you get all sorts of terminology about the formal patterns and designs of good object-oriented programming. However, that’s not what this article is about. Instead, I’d like you to think about the basic structures that you already use when you write code, and then I want to help you think about how you might approach them differently in an ILE RPG environment.
For example, back in the RPG/400 days, I wrote many interactive applications, and after a while, I found that I always followed the same steps when showing a screen to a user:
- Load the data to be displayed on the screen.
- Display the screen and wait for user input.
- Clear the error message from the screen (if one was displayed).
- Check for command keys.
- Check that the data the user entered is valid.
- If it’s not valid, go back to step 2.
- Perform processing based on the user’s input.
If all is well, I process the input. This example shows a snippet from a customer maintenance program. This routine asks the user for a customer number, and if all is well, shows a second screen on which the user can change the various fields of the customer master record.
I used the same pattern with the second screen (the code is not shown here, but you can download it from SystemiNetwork.com/code). It too follows the same steps: displays the screen, clears any error messages, handles any function keys, checks the fields to make sure that they’re valid, and if all is well, updates the file.
What’s Wrong with That?
This pattern is simple to code and simple to read. It’s a nice example for programmers writing their first interactive program, because it’s easy to understand. In the real world, however, it’s not so great. It’s simple to write but hard to maintain. My company will have dozens, if not hundreds, of programs that need to read and write customer data. If I want to change my customer file, every program written this way will have to be analyzed, changed, and retested!
What happens when I want to edit customers from a different program? Perhaps when an order is placed in my order entry program, it should automatically display the edit screen so the operator can verify (and if needed, change) the customer’s name and address. Now I have to modify my program to receive the customer number as a parameter instead of asking for it on the second screen. Or, perhaps I’d write a whole new “edit” program separate from this one. Then I’d have two to maintain and two places to change my logic if a business policy changes.
What happens when my company decides to let customers place orders on the web? I write yet another customer editing module?
Surely other applications besides the maintenance program will need to retrieve a customer’s name and address. Wouldn’t it be nice to reuse those parts of my program without having to rewrite them each time?
Sure, I could copy and paste the code from one program to another, or even use the /COPY compiler directive to bring different pieces of the program together, but I still end up with lots of program objects all using the same code. If I change the code in one place, I have to change it everywhere, or the business rules are no longer consistently adhered to. In the case of /COPY, if I change it, I have to retest everything, or I more than likely will cause a program to fail.
When changes become difficult to make without lots of analysis and retesting, you have a problem. Your system is inflexible. You can’t make changes that help your business improve or grow because you’re too worried about breaking existing code. Suddenly, the business has to write its business rules to conform to IT instead of the other way around.
Time to Learn New Patterns
When you try to use ILE programs that provide the benefits of ILE, it’s hard to keep using your old patterns. This is where you start cursing ILE and can’t figure out where to put your file access, or where to divide your code, or how to pass error information. You need to come up with new patterns for your programs!
Many experts recommend adopting a Model-View-Controller (MVC) strategy for writing new applications. In this architecture, the “model” is a module that contains all the business logic. The “view” is a module that interfaces with the user. The “controller” is the code that controls the flow of your program and passes the business data to the user interface, and vice versa. Many experts also suggest that you have a “database” module that’s separate from the model as well. That way, you can write a different mechanism for the storage of data, if required.
The key to this model is that none of the components should know or assume anything about how the other components work. The model should not know or care whether it’s receiving its input from a green-screen terminal, a web page, or a batch program. It should not care whether its output is displayed in a subfile or printed on a report or used to create a graph.
Likewise, the view shouldn’t know or care how the model works. It doesn’t need to know how tax is calculated or how the company determines the price of a widget. Its job is to present data to the user. That’s all it should know about.
The controller should know only how to call the model and view procedures in the right sequence. It does need to know a bit about each one, but it shouldn’t know or care what the specifics are. For example, it might know that your view is a web page, and therefore that it accepts one input and returns one output and must call the model and view routines in the appropriate sequence. But it shouldn’t need to know anything specific about whether the output is HTML or XML, or that you have to write a subfile record in a loop. That’s the view’s problem. Likewise, it shouldn’t know how the model came up with the information that it returns. That’s the model’s problem.
The database module is typically used only directly from the model and is completely unknown to the other components. A separate database module would be useful if you want to switch from RPG’s record-level access (RLA) to SQL, for example, or perhaps store the data in XML instead of a database. Or if you wanted to store your data on another system, you could change the database module and have everything else still work without any of the other components even knowing about the change.
This different, more flexible, approach requires you to develop new patterns instead of using the same ones that you’ve always used. When you get good at these patterns, you’ll be able to develop new, flexible applications as easily as you did the old ones.
Business Logic Module (or “Model”)
One thing that made my RPG/400 pattern hard to maintain was that it does field and screen displays in a loop together. If I want my screen logic (the “view”) to be reusable by other programs, it has to be decoupled from the business logic. Similarly, if I want the business logic to be reusable, it has to be decoupled from the display logic. The controller logic passes the data between them. Although the overall logic is the same, the pattern has changed a lot.
The most reusable part of the new design is the business logic. I follow a pattern like this when I write my business logic:
- The business logic consists of many individual routines.
- The individual routines all have well-defined parameter lists. Global variables are never made accessible from outside the business logic itself. This way, I have to change or retest the calling programs only if I change the parameter lists themselves.
- An Init() procedure and a Done() procedure do the setup and cleanup, respectively, for the service program. These are always called by the other routines in the same module for initialization and cleanup.
- The Init() and Done() procedures can also be called by the caller if it’s necessary for the caller to control when the service program’s files are closed and internal variables are reset. Usually, I’d use activation groups to handle this, but for performance reasons, sometimes the caller needs to be able to control it directly, so all my service programs make the Init() and Done() routines available to the caller.
- The rest of the routines handle one business function at a time. What sort of things do you do with customer data? You load it into memory, you read information about it, you set information about it, you save it back to disk. Each of these actions is a routine that can be called independently. I’ll use this same business logic for every program on my system that works with customer data. For this reason, I’ll have other routines in my service program besides those used by the maintenance program that I’m demonstrating in this article. For example, there might be a routine to calculate a customer’s current balance or the date that the customer last ordered.
Whenever I write a module, I give it a name that relates to its function. In this case, it relates to customers, so I want to call it CUST. In my shop, we have a strict standard that all module names end with a two-character suffix that identifies the language that the modules are written in. Therefore, my module is named CUSTR4.
When you’ve been writing modular code for a while, you end up with a ton of different routines in different service programs. If you’re not careful, you eventually run into a naming conflict in which two modules each have a routine with the same name. That can wreak havoc on maintenance! Therefore, I always prefix every subprocedure, and anything else that can be used from outside the module itself, with the module name, but without the language suffix. In this example, all subprocedure names, data structures, and so on will start with cust_. Therefore, I have a cust_init() procedure to initialize the module, a cust_done() procedure to clean up the module, a cust_load() procedure to load a new customer into memory, a cust_getName() procedure to retrieve a customer’s name, and so forth.
Figure 2 shows the routines that initialize and clean up my module. In this case, I use those routines to open the files used in the module. In a more complex module, you’d see many open statements (A in Figure 2), each opening a different file. When all the files have been opened, I call the CEE4RAGE API (B), which tells the operating system to automatically call my cust_done() procedure when the activation group ends. That way, if the controller doesn’t want to clean up the module, or if something prevents the controller from doing its job, the operating system still calls my cleanup routine.
Figure 3 demonstrates loading a customer’s data into memory. Every routine in my service program follows a pattern similar to the one in Figure 3:
- Call the Init() procedure to initialize the module, in case it hasn’t been done already (A in Figure 3).
- Perform the business logic that the routine is designed to perform.
- If an error occurs, call a procedure that stores the “last error that occurred” and a corresponding error code (B). I talk more about that in a minute.
- If an error can occur in a subprocedure, the subprocedure should return some sort of error code to indicate this to the caller. I usually prefer to return an indicator set to *ON for success or *OFF for failure.
Whenever “fields” are exchanged between the business logic and the display logic, I use a “getter” or “setter” routine to do so instead of accessing the service program’s fields directly. This lets me validate the fields as they’re set, putting the validation logic in the business module where it belongs. Figure 4 demonstrates a getter (A) and a setter (B) for the customer’s name.
In addition to providing a layer of protection for the fields in my application, getters and setters give me control over backward compatibility. For example, if I want to increase the customer name field to 50 characters, I can rewrite my getter and setter as in Figure 5. Any preexisting programs call the old getName() and setName() routines, so they continue to work as they always have (no need to recompile them). On the other hand, new programs that want to leverage the additional space can use the getNameLong() and setNameLong() routines. With this strategy, the only program that I have to recompile when I change my field layout is the CUSTR4 service program.
Not all getters and setters will deal with a single field. I try to group the data passed back and forth into a logical unit of some sort. For example, the cust_getAddress() and cust_setAddress() procedures in Figure 6 accept a data structure as a parameter. The data structure lets me pass the entire address as a single parameter. I define a template for the data structure and place that in a copybook along with the prototypes for the subprocedures, and I use the LIKEDS keyword to make a local copy of the data structure where it’s needed.
Error Handling
Each module that I write has a SetError() subprocedure called by internal routines to set two fields, an error number (useful for programs that want to handle certain situations), and an error message (useful for displaying to the user). Callout B in Figure 3 demonstrates how I call one of these SetError() routines. The error numbers always have named constants (also included in the copybook for the module), letting the next programmer more easily understand what the error number means. Callout A in Figure 7 demonstrates how to set the error information; it merely accepts the parameters and uses them to set variables that are global to the module. To make these values available to other modules, there’s a special getter procedure called cust_error(). This procedure (B in Figure 7) returns the message and optionally passes the error number back in a parameter.
This error handling method is another pattern that I repeat in all my applications. It’s a simple and effective way of communicating error information from module to module without cluttering up the parameter lists of each procedure.
The Display Logic Module (or “View”)
In this example, I’m working with a green-screen program. The display logic is simple. It accepts parameters for the various items that the user can edit, as well as indicators that represent the various functions that the user can request. Figure 8 demonstrates a routine to display a screen that asks for a customer number.
The parameter list for the display should be general enough that it can apply to different user interface types. In this case, I’m using a green screen, so pressing F3 and F10, respectively, sets the Exit and Add parameters. However, in a GUI application, I might have OK, Add, and Cancel buttons instead of function keys. In a batch program, these functions might be controlled by additional program logic. The point is, keep your procedure interface generic enough that you can use the same parameter list for other “view” modules.
I show only part of the code for the display module, but you can download the full versions of all the source code in this article at SystemiNetwork.com/code.
The Controller Logic
Binding it all together is the controller logic. Figure 9 shows the part of the controller logic that displays the first screen, the one that asks for the customer number. It calls the display logic to get the customer number, and then the business logic to validate it and load the customer into memory. The next stage of the controller (not shown, but available at SystemiNetwork.com/code) calls the getters and setters in conjunction with the display logic to let the user edit the customer master record.
Do I Really Need All This?
You might not. The biggest benefit of using modular patterns to design a program comes from keeping the display logic separate from the business logic. In this example, I keep the controller logic separate, but that’s not necessary. In my experience, you still get about 95 percent of the benefits of an MVC design even if the controller logic and display logic are in the same module.
I often create only two modules for a program. One contains the controller and view combined, and the other contains the combined business and database logic. This architecture works well for small or simple projects. When working with large or complicated projects, I recommend using three or four modules and a full-blown MVC architecture.
Follow the Pattern
Although modular programming might seem overwhelming at first, after you’ve established patterns for yourself, it becomes much easier. To help you get started, download the code for this article and use it as a template for your own applications. Before you know it, you’ll be an old hand at these new design patterns