|
Joe, There are several points you fail to see and understand: 1. I agree, many more complex transactional systems involve custom logic such as price calculations, WIP, MRP, etc. Nothing new or scary here, this is standard application development from my perspective which can be easily handled within a framework as I'll describe, first we need to examine how you do your "complex calculations" because they themselves heavily are based on reoccurring patterns. 2. Most application development tasks, calculations, whatever you want to call it, breaks down into more fundamental subprocesses. These most basic processes are: a. Getting data by various criteria (query / read, set lower limit and chain, etc ) b. Updating data (with complex validation and computations at times) c. Deleting data d. Inserting or adding data e. Processing and decisions and calculations before and after all of the steps above. To make your "complex calculations" you normally are getting data from the current customer, their past orders, their status, their default currency, etc. After gathering the decision criteria, you apply some math or logic or business rules and then probably write some "calculation" or result out to your database. They only thing custom in this whole process is doing the calculation or business rule. 3. Most applications and development projects spend the MAJORITY of their time and resource doing reoccurring tasks and a smaller percentage doing custom logic BECAUSE custom logic usually involves using these reoccurring tasks. Applications vary but normally this holds true. Let me provide a concrete example and some code snipits to further illustrate. Business Scenario: Wabash National, one of largest transportation manufacturer's of tractor trailers in the world, had a fax and MS Access based solution to manage their warranty process. Wabash provides trailers to Fed Ex, JB Hunt, Schneider, Swift, etc. The fax and MS access system could not keep pace with the 10's of thousands of warranty claims made each year. We developed a system for them using 100% Java working against their backend DB2 files. This system is used by hundreds of service centers and part vendors as well as Wabash warranty department. Processing could be complex but fit perfectly in an OO model. I could provide dozen examples but let me simplify with just a couple. When a claim was submitted by a service center (responsible for making a Warranty repair) we had to compute the claim total. The claim amount was based on a) Who is the service center and where are they located. Service centers could be Canadian or Mexican or American, all claim amounts need to be in the currency of the service center and you need to know the current currency conversion which JDE files provided b) What parts where used on the claim c) Are these parts taxable d) Where these supplied by Wabash or the service center; we only pay if we had to supply the parts e) What is the amount of labor in hours, and what does the service center charge per hour, etc. Here are code snipits that processed those business rules. The basic process still reduced down to the reoccurring patterns I described above with some custom logic: Major Objects/Classes: Row - Represents an actual data record Field - Represents a column within a Row such as a SSN, ZipCode, PO#, etc RowCollection - A collection or Rows returned from a query request Sequence: a) Service Center is about to submit a claim, framework or pattern automatically invokes the "preInsert" method on the ServiceCenterRow (subclass of ClaimRow which subclasses base Row). b) ClaimRow overrides the preInsert method which gives any and all objects a chance to handle "complex processing" before an insert occurs /** * Handle Claims submits considering if the repair has already been made * */ public void preInsert(ExecutingContext pContext) throws CMException { // Check what process we are performing String mrKey = (String) pContext.getObject(WarrantyRow.MAGIC_KEY); if (mrKey == null) { //throw new CMException("Internal Error - ServiceCenterRow.preUpdate could not get key from request."); } // The repair has already been completed, so handle it if (MAGIC_REPAIR_COMPLETE.equals(mrKey)) { prepareForRepairComplete(pContext); } // The repair is submitted for approval else if (MAGIC_SUBMIT_TO_WNC.equals(mrKey)) { prepareForSubmitToWNC(pContext); } } The claim is being submitted: preInsert calls this method to prepare a Row for submittal /** * Handle processing for a claim about to be submitted */ public void prepareForSubmitToWNC(ExecutingContext pEc) throws CMException { // check if claim has total greater than zero double total = getValueAsDouble(CLAIM_TOTAL_AMOUNT); // Claims with a zero amount are not allowed , throw an exception which the framework will automatically propagate to the UI if (total <= 0 && !hasWNCSuppliedPart()) { throw new ValidationException("Invalid claim total.", "Claim being submitted to WNC must have a claim total greater than $0.00."); } // set the service center status of the claim to WNC pending setFieldValue(STATUS, STATUS_WNC_PENDING); // set the WNC status of the claim to pending setFieldValue(WNCSTATUS, STATUS_WNC_PENDING); // Based on the trailer VIN, go to the master trailer table, get and automatically copy Trailer information into this ClaimRow // Information copied includes Manufacturer, year built, manufacture line, model, year sold, year put in service Field vin = getField(VIN); if (vin != null) { RowCollection vinInfo = getVinInfo(); // Method gets the VIN record // we are only going to take the VIN info from the first row that was returned. if (vinInfo != null && vinInfo.size() == 1) { Row row = vinInfo.getRow(0); // Generic method to copy from Row to Row based on common field names. this.copyIntoRowFieldsWithCommonFieldNames(row, true); } } } c) Framework goes on to call ServiceCenterRow.validate where both generate and custom validation can occur. During the insert process, the currency rate has to be determined depending on the service center making the Claim. (Canadian, USA, etc) Claim total is adjusted per currency before insert occurs /** * Return the currency conversion rate used to determine the total claim amount */ public BigDecimal getCurrencyExchangeRate() throws CMException { try { BigDecimal currency; // Check if the currency rate has already been set on this claim Field er = getField(CURRENCY_EXCHANGE_RATE); Field cc = null; int status = getStatus(); if (status == IClaimmastTable.STATUS_DRAFT) { // if currency is not set then try to get it from contactbu int buid = getServiceCenterBuId(); if (buid == -1) { return one(); // default to USA } // Get the actual service center Row based on this contact id ContactBURow contact = getContactBu(buid); // !!C MT (8/28/2003 10:37:20 AM) - There should always be a contact bu associated with a claim if (contact == null) { if (isLegacy()) { return one(); } throw new CMException("Internal error. ClaimRow.getCurrencyExchangeRate could not retrieve information for the contact bu</H4>"); } // Get the country currency for this service center cc = contact.getField(IContactBuTable.COUNTRY_CURRENCY_CODE); // default to 1 if not specified if (cc == null || cc.getValueAsString() == null) { return one(); } // based on the currency code, go get the current exchange rate for today, uses JDE conversion tables currency = getCurrencyExchangeRate(cc.getValueAsString()); // set currency on row er.setValue(currency); } return ((BigDecimalField) er).getValueAsBigDecimal(); } catch (Exception ex) { throw new CMException(ex, "ClaimRow.getCurrencyExchangeRate"); } } D) Insert of a claim occurs. 4) So I'm able to add custom code and logic of unlimited complexity using EITHER procedural or OO techniques. The real benefit is that I only have to add the custom code at predefined framework points. Predefined extension points are are numerous such as: preInsert, postInsert, preUpdate, validate, initialize, etc.... Where ever the framework is at work, I'm able to jump in and handle any special cases I need to and ONLY IF I NEED TO. <Joe> Paul, there is no easy way to do this, so I'm going to be blunt. The reason I didn't respond to your last post was because you seemed to miss the point of my post where I separated business programming into file maintenance, executive inquiries and transaction processing. The vast majority of your patterns handle only my first category of applications, essentially master file maintenance. No matter how complex, file maintenance is file maintenance. You've written one Work With program, you've written them all. And OO probably can be made to handle that portion of the load just fine, provided one is willing to back away from JDBC and provide a real security layer. </Joe> In your procedural case and approach you are writing redundant code for occurrence of the pattern versus once per pattern. How would you handle special logic and processing with the procedural approach in the case where you may be maintaining Claims or Invoices or Customers and depending on the type you are updating need to incorporate special logic? For example, before updating an invoice it needs a valid PO, before updating a customer it needs to verify credit limits. With OO and frameworks, you simply override the "preUpdate" method adding any logic needed. The frameworks calls you and you can veto the update if you want to. Procedural is reduced to monolithic "if then else" code again handling each update process case by case. So I disagree with you here, you can copy or generate some common "work with " utility but is will not be flexible and extendible and will not meet the business requirements at least that I'm faced with. <Joe> Basically, you're talking about the very first layer of business programming, that of entering and validating data records. I'm not trying to minimize the issue; what you're talking about is certainly an important part of any business application. But it isn't what I talk about when I talk about business applications. I'm talking about the hard stuff, which I've expressed over and over again: pricing calculations, finite forward scheduling, batch balancing, MRP generations, WIP calculations, standard costing, all of the stuff that doesn't come from the database, the stuff that differs from client to client, from customer to customer, from day to day. It is this level of programming that I have yet to see anyone properly design an OO framework for. </Joe> I'm talking about all levels of programming. I've effectively used these techniques on applications of all size and they work. The framework handles common processing, you tack on your custom processing. This is the industrial revolution for software. The ability to pump out mass application with the easy ability to customize where needed. If you would like, I can show you overridden code of any complexity. The process is gather your data, make your decisions, return your result to the framework. If no complex decisions need to be made, I do nothing and the standard framework processing handles everything needed. <Joe> In business application programming, issues like subfile paging and field validation are secondary. It is assumed that all the critical information has been parsed and validated. Now it is time to actually PROCESS that information, and it is this level of programming where OO is less than satisfactory, in terms of productivity, performance and flexibility. I am convinced that there is no way you can program an MRP generation process in Java in the time I can do so in RPG. And after you're done, I am confident that I can throw some real world wrinkles into the business requirements that will take you far longer to implement in your OO environment than it would take with my procedural programs. </Joe> If you truly analyze the time you spend developing an application you will find that the majority is spent on recording task as I've explained. Your custom logic of pricing, MRP generations, etc is probably a distant second in terms of time spent. As you mentioned, to do those calculations, you are getting data, etc. The framework can get, cache, query, update, whatever you need without effort. Therefore I know solving these reoccurring problems would yield far more benefit to your productivity than being concerned weather Java can handle BigDecimal precision, and if then else logic. RPG would probably win in a performance race depending on the situation. Again, I'm not anti procedural nor anti RPG. Leverage them where you can. Can you explain how the procedural approach would handle paging/subfile processing from file to file along with the rest of the patterns I've described? Applications of any level that you describe can be abstracted into these patterns and done effectively with OO. So with all due respect, as I've said before if you are challenging me in any of the three application areas you mentioned, I'm ready. You should also understand the majority of Web based applications being developed today ARE of a maintenance, or EIS inquiry in nature. To a much lesser extent, large projects in Java are occurring. We've started a GL rewrite in Java a few weeks ago. Again, I make my living solving today's business needs and they are as I mentioned. <Joe> It is simply a matter of the nature of the language. While you are busy trying to determine the object hierarchy of material requirements and identifying the interface for the factory class that converts order entry detail lines into soft allocations, I'm already writing the calculations and working on the detail, because I can use those records as is. By being as close to the database as RPG is, I don't have to go through the intermediate layer of defining objects and their attributes. Your only hope to even stay with me is to simply execute raw SQL statements in your factory to directly derive the objects from the database and then figure out some way to order them in collections. With million-record MRP generations, that's no small feat, by the way. And by the time you've done that, I'm already processing the results (not to mention the fact that mine probably runs a lot faster). </Joe> At any time, I can drop down to writing procedural code very similar to what you mention. Subclassing and inheritance are only used if they provide a benefit. It is not difficult to have a routine to go to the raw data as you mention. Here is code for a client that notifies the buyer via email when a supplier changes a purchase order. Again, a simple override of standard methods. Uses configurable property to make business decisions. This method is called by the postUpdate() via the framework. /** * Vendor has changed the PO! Send an email to Buyer */ public void emailBuyer(ArrayList changes) throws CMException { ExecutingContext ec = ExecutingContext.getCurrentExecutingContext(); boolean isWarn = cat.isEnabledFor(Priority.WARN); // Get the configured SMTP server which is simply a field in a standard config DB, generic method to get config properies String smtp = (String) getConfigProperty(IEmailObject.CONFIG_KEY_SMTP_SERVER, ec); if (smtp == null) { if (isWarn) { cat.warn("No SMTP server configuration property exists for owner. Therefore no email can be sent."); } } // need to retrieve the Buyer's email from the Buyer authentication file SQLContext context = new SQLContext(getSystemAlias()); context.setCacheResults(false); // no need to cache this data String library = getConfigProperty("DATA_LIBRARY", ec).toString(); // library and file can changed based on division String buyerTable = getConfigProperty("BUYER_TABLE", ec).toString(); String sql = "select BMEML from " + library + "." + buyerTable + " where BMBUY = '" + getBuyer() + "'"; context.setSQL(sql); // get the email Row using a Factory getter RowCollection rc = DataEngine.getRows(context); if (rc.size() != 1) { throw new CMException("Unable to determine a recipient for the email."); } // Find the email field within this Row String email = rc.getRow(0).getFieldWithUsageId(EMAIL_FIELD).getValueAsString(); if (email == null) { cat.warn("No email address exists to send an email to. Therefore no email can be sent."); } } String body = constructBody(changes, System.currentTimeMillis(), getVendorNumber().intValue(), getPO().intValue(), getLineNo().intValue()); //getEmailBody(ec); List to = Arrays.asList(new String[] { email }); // who is the mail going to? List replyTo = Arrays.asList(new String[] { "suppliernet@xxxxxxxxxxxxx" }); // hardcoded, perhaps should be a config property CMMailer mailer = CMMailer.singleton().createMailer("", "", smtp); // create message CMMailMessage mail = new CMMailMessage(); mail.setContentType("text/html"); // send from mail.setFrom("suppliernet@xxxxxxxxxxxxx"); mail.setAuthenticate(false); // set subject mail.setSubject("SupplierNet: PO:" + getPO() + " Line#: " + getLineNo() + " changed by Vendor."); // set who sent to mail.setTo(to); // reply to mail.setReplyTo(replyTo); mail.setBody(body); mailer.sendMessage(mail); <Joe> In a procedural language, such programming requires an intimate knowledge of the database, a detailed understanding of the business requirements, and some really solid programming skills. It's a completely different process than creating yet another "Select and Display" program. </Joe> In OO, we have to fully understand the requirements as well. The Select and Display pattern actually involves distinct sub patterns consisting of "select records" and return them to me while "Display" involves forwarding a group of records (RowCollection) to a display engine. If I want to get records I use the select as shown above. <Joe> I could go through a similar analysis of the pricing issue. Here, the area where the system breaks down is the sheer number of possibilities. You end up with either an unwieldy and brittle hierarchy, or else you devolve into writing procedural code inside of your objects. And once/Joe you've done that, to me you've basically just accepted the worst of both worlds. Paul, I don't discredit your abilities, your credentials, your experience, or your accomplishments. I think you've done a terrific job on one segment of the business application spectrum. I'm simply restating my opinion that in situations where non-repetitive programming is required, OO is less flexible and less productive in meeting day-to-day business requirements than procedural languages and particularly RPG. </Joe> I disagree again. Frameworks can handle the routine work, you add you custom logic where and when needed. To produce business applications of any type, you need to incorporate these common reoccurring requirements. How many applications (even your MRP) that you write don't involve selecting records, updating, displaying, processing, validating, inserting, etc? As I mentioned as a friendly educational and learning project, if you want to challenge your approach against what OO can provide, I'm ready. (Actually I'll be at a client next week so I'm ready this week :^) Joe Respectfully, Paul Holm
As an Amazon Associate we earn from qualifying purchases.
This mailing list archive is Copyright 1997-2024 by midrange.com and David Gibbs as a compilation work. Use of the archive is restricted to research of a business or technical nature. Any other uses are prohibited. Full details are available on our policy page. If you have questions about this, please contact [javascript protected email address].
Operating expenses for this site are earned using the Amazon Associate program and Google Adsense.