PRS Driven Programming

It’s been a while already since I have worked on our product. Here we have been very strict about using PRS, so allow me to share a few things with you.

First, I am not a member of the PRS group, however my boss Eric Wauters (Waldo) is. Everything I will say after this will be my personal view on PRS, nothing of this has been whispered into my ear by PRS pro or con oriented people 🙂

What is PRS?

PRS stands for Partner Ready Software. It is an “ideal” that has been launched by a few well-known individuals in the NAV world.

It basically suggests to program in a certain way, so that future programming/debugging should be easier, faster, more logical, reusable, …

What does PRS cost?

PRS is just a concept, a methodology.

You choose if you want to work using that methodology. It is thus completely free.

What are these methods exactly then?

Glad you asked… 🙂

Let me discuss a few of these:

Hooks

We try to change as little of the standard NAV objects as possible.

Basically, for every NAV object we need to change, we create a CodeUnit which “hooks” into the standard NAV object.

For a Sales Header we would have a Sales Header Hook. For CodeUnit 80 we would create a Sales-Post Hook.

When necessary to change the default object we reference to a function in these hook CodeUnits, at most we create a function on the source object where we forward the code to the hook.

We only use the hook CodeUnit in the object the Hook is for.

 Pros / Cons:
+ All Code is grouped together
+ You always know where the code is
– You need a lot of CodeUnits

Atomic Coding

We create functions with names that mean something, or which exactly describe where they are positioned.

We keep them as small as possible. This works also closely together with the hook CodeUnits.

Examples:

When adding code to the insert trigger of a Sales Header, we add a function to our Hook: OnAfterInsert.

As last line on the insert trigger, we call this function. Any code that needs to be added, will be in the hook.

You always know when this code will be executed as well: after the insert trigger has run.

When deleting a Sales Line in our product a few things need to be checked. We find our Sales Line Hook, Function OnAfterDelete. Instead of writing all code there, we see functions. This is an example in our product:

I guess you get the picture? Basically, even a functional consultant would be able to look at the code. He would open the sales line, and see the delete trigger, he could follow the “OnAfterDelete” with “Go To Definition” and see this. Immediately it is obvious what happens.

Sometimes it is not always as easy as OnAfterInsert / OnAfterValidate / …

In our product, we needed to change a lot of CodeUnit 80, this is not always “OnBefore” or “OnAfter”, but sort of “InBetween”. Here are a few of our hook functions of CodeUnit Sales-Post:

OnBeforePostDocument
OnBeforeInsertShipmentHeader
OnAfterInsertShipmentHeader
OnDivideAmount_CalcVATBaseLowered
OnBeforePostCustEntry
OnAfterUpdateInvoicedQty_OnShipmentLine_WhenOnlyInvoicing

Another advantage of this with a real live example?

When upgrading to NAV2013 R2, we noticed a lot of code that had been moved around in Sales-Post.

Our hook functions weren’t on the correct positions anymore, but our functions had 2 benefits:

  • They describe were they should be
  • They are only 1 line, so easy to move to the correct position again

Pros / Cons
+ Readable
+ Reusable small functions
+ obvious meaning of what a function does
+ Works great together with “Go To Definition”
+ Easy for upgrading
– Can’t actually think of any…

Object Oriented Programming

Well, NAV just isn’t object oriented, how do you do this then?

We look at our tables as Classes, they have properties (Fields) and methods (Functions).

Instead of creating a CodeUnit with a function SendSalesOrderConfirmationByEmail, we create a function on our table: EmailOrderConfirmation.

Pros / Cons
+Usable on every place you use the source object
+No need to pass the record to the function, you already have the record
– A lot of functions on the source object

No Comment

This seems a bit odd, but when already using the above, you have clear readable code, with logical, descriptive names. Splits into the smallest possible chunks.

Most of the time, people add comment, because the code is unreadable… When using atomic coding, code should be more readable already.

UI Separation

Everything that requires user interaction is put in a separate CodeUnit. A confirm, STRMENU, Page.RunModal, …

With atomic coding, each user interaction will have its separate function, which MUST check on GUIALLOWED and allow the user to bypass user interaction with a default parameter.

Pros / Cons
+ All user interaction grouped together
+ Possible for batch processes to bypass user questions
– Again, extra CodeUnits

Iface / Facade

Using Interfaces and Facades is a nice concept which can sometimes be a bit hard to understand. Let me explain by a real life example in our product, the notification framework.

The point of this framework is to allow something/somebody to send something to something/somebody using some method.

Get it?

This could be:

  • Send a report in PDF by email to the customer
  • Send a note to another user
  • Alert the customer by SMS that his order is ready to pick up

Alright, let’s split this up in chunks:

Using some method

There should be some set up table which holds a CodeUnit. This CodeUnit will accept some information to be sent: From, To, CC, BCC, Subject, Body and Attachments.

When a notification is send, we call the facade CodeUnit supplying this information and the method (Code) how it must be sent. The facade CodeUnit will look which CodeUnit needs to be fired and knows how to present that information to that CodeUnit.

When I need to create something that faxes, I create 1 CodeUnit which accepts all the parameters required and sends the fax. Then only setup is needed.

Send something to something/somebody using some method

We create some sort of class which accepts to send something to something/somebody.

This class has properties like To, From, CC, BCC, Body, Subject, Attachments, Method to send

Finally this class has a method to send the notification

It is still not aware of what is send by who on which method.

This CodeUnit is called an interface (iFace) CodeUnit. This iFace should be the only place where the framework should be  used from outside of the framework. It will contain functions to set the properties and the methods which are available to outside of the framework.

On the Send Method, the framework may check certain data, if the send method supports attachments for instance, which will be a separate function, but this is not a method which needs to be called from outside of the framework.

Allow someone/something to………

If anyone wishes to use this framework, they reference the iFace and see those (and only those) functions which are needed.

The user of the iFace will know what he wishes to send to where and through which method. He doesn’t care for some basic checks and how to sending is handled.

In the Notification Framework we have a separate Framework above this, which allows to add Templates in multi-language to set body/subject/… by default, this Template Framework has its own iFace and sends the data through to the Notification Framework itself after manipulating some of the data.

Let’s have a complete example with all of the above 🙂

I have a page which holds a Sales Order. There is a button on it to send the order confirmation as a PDF, by opening it in Outlook with a preset body & subject.

  • The page will call the Sales Header “SendNotification” function
  • This function will continue to the Sales Header Hook
  • The function saves the report as PDF and pass it to the Template Framework iFace, together with the “To” email address of the customer, and his language code
  • The Template Framework will fill the body/subject with the data in the template
  • The Template Framework will call the Notification iFace with all information
  • The Notification Framework will do some basic checks and log everything
  • The Framework calls the Facade to handle the “Outlook” send
  • The Facade knows which CodeUnit can handle this send method and calls the CodeUnit with all necessary info
  • The handler CodeUnit creates an Outlook message and adds the PDF

This is our result:

 Notification Result

This all sounds great, but does it actually work?

I am certainly convinced as you might have understood. Breaking up functions in small chunks make it possible to reuse them.

We have Function CodeUnits with specific String Functions, File Functions, ….

These are reused all over the product without the need to write them over and over and over again, resulting in saving time for other stuff. If there is a bug, you change it in 1 place instead of thinking what other location might have copied it…

When upgrading from NAV2013 to NAV2013 R2, most of the merging was single lines and thus very little work.

If you need to change something on the sales header insert trigger, you know where to look for customization code, instead of browsing to hundreds of lines of standard NAV code.

I also do not care how a framework works, by calling the iFace I have a clear overview of what the functions on the outside are

I think I’ll wrap up this post here, because I just could keep going.

If anyone is interested, I might do another post with actual examples like our Report Document Templates or something. You can use the section below for comments and/or questions.

For more information, remember to visit the PRS website or contact any of the people behind it: Eric Wauters, Gary Winter, Mark Brummel & Vjekoslav Babić

7 thoughts on “PRS Driven Programming

  1. Well, Waldo should have written this blogpost but he is probably too busy with PRS! 🙂

    Please continue to write about PRS!

    I would like to implement it also and having a lots of real-world examples helps to convince everyone.

  2. Pingback: PRS Driven Programming | Pardaan.com

    • In which way do you mean to see an example? Just the functions that are in the iFace codeunit which is to be used outside of the framework?

      Below are the functions in de iFace codeunit, these all patch through to my management codeunit
      CreateNotification(NotificationTypeCode : Code[20];SourceRecord : RecordRef)
      SetFrom(FromName : Text[250];FromAddress : Text[250])
      SetTo(ToAddress : Text[250])
      SetCc(CCAddresses : Text[250];CCAddressSeparator : Text[1])
      SetBcc(BCCAddress : Text[250])
      SetSubject(Subject : Text[250])
      SetBody(Body : Text)
      SetShowError(ShowError : Boolean)
      AddAttachmentStream(VAR AttachmentStream : InStream;FileName : Text[250];FileExtension : Text[250])
      AddAttachment(FilePath : Text;FileName : Text[250];FileExtension : Text[250])
      AddAttachmentFromServerFile(FilePath : Text;FileName : Text[250];FileExtension : Text[250])
      SendNotification() : Boolean
      GetLastError() : Text

      • btw, the template interface has these functions:

        CreateNotification(TemplateCode : Code[20];LanguageCode : Code[10];NotificationTypeCode : Code[20];SourceRecord : RecordRef)
        ParametersToApply(ParameterString : ARRAY [10] OF Text)
        OverrideFrom(FromName : Text[250];FromAddress : Text[250])
        SetTo(ToAddress : Text[250])
        SetCc(CCAddresses : Text[250];CCAddressSeparator : Text[1])
        SetBcc(BCCAddress : Text[250])
        OverrideSubject(NewSubject : Text[250])
        OverrideBody(NewBody : Text)
        SetShowError(ShowError : Boolean)
        AddAttachmentStream(VAR AttachmentStream : InStream;FileName : Text[250];FileExtension : Text[250])
        AddAttachment(FilePath : Text;FileName : Text[250];FileExtension : Text[250])
        AddAttachmentFromServerFile(FilePath : Text;FileName : Text[250];FileExtension : Text[250])
        SendNotification() : Boolean
        GetLastError() : Text

        You might notice a few functions here are named “override”, this is because these parameters should come from the template

        These are my top interface which prints reports to PDF and sends them to the Template Interface:

        CreateNotification(VAR SourceRecord : RecordRef;LanguageCode : Code[10])
        SetHideValidationDialog(SetHideDialog : Boolean)
        SetTemplateAndNotificationType(TemplateCode : Code[20];NotificationTypeCode : Code[20];ExportType : Integer)
        ParametersToApply(ParameterString : ARRAY [10] OF Text)
        OverrideFrom(FromName : Text[250];FromAddress : Text[250])
        SetTo(ToAddress : Text[250])
        SetCc(CCAddresses : Text[250];CCAddressSeparator : Text[1])
        SetBcc(BCCAddress : Text[250])
        OverrideSubject(NewSubject : Text[250])
        OverrideBody(NewBody : Text)
        SetShowError(ShowError : Boolean)
        SendNotification() : Boolean
        GetLastError() : Text
        AddReport(VAR SourceRecord : RecordRef;ReportID : Integer;FileName : Text[250])
        AddReportSelection(VAR SourceRecord : RecordRef;ReportSelectionUsage : Integer)
        AddAttachmentStream(VAR AttachmentStream : InStream;FileName : Text[250];FileExtension : Text[250])
        AddAttachment(ClientFilePath : Text;FileName : Text[250];FileExtension : Text[250])
        AddAttachmentFromServerFile(FilePath : Text;FileName : Text[250];FileExtension : Text[250])
        — Single Line Calls —()
        ReportNotification(ToAddress : Text[250];LanguageCode : Code[10];ReportID : Integer;FileName : Text[250];VAR SourceRecord : RecordRef) : Boolean
        ReportUsageNotification(ToAddress : Text[250];LanguageCode : Code[10];ReportSelectionUsage : Integer;VAR SourceRecord : RecordRef) : Boolean

        • Hi Magno,

          Thanks for the reply.

          I was referring to the line where you said:
          “If anyone is interested, I might do another post with actual examples like our Report Document Templates or something. You can use the section below for comments and/or questions.”

          I think that your reply and the example in this post kind of sorted it out for me.

          Thanks!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.