Patterns - quandis/qbo3-Documentation GitHub Wiki
QBO includes patterns across many of our modules, including:
| Pattern | Description |
|---|---|
| Naming | Standard naming patterns and terminology. |
| GenericObject | Class that can be bound to any parent. |
| Templates | A template describes how an instance should behave. |
| User Interface | Common patterns used when implementing the user interface. |
| Data Table Inheritance | Normalization of data and functionality across tables. |
| History | Audit trail pattern implemented by the database. |
First, some terminology:
| Term | Definition |
|---|---|
| Table | A table persisted in a database. E.g. the Message table stores messages. |
| Module | An application tier class, written in C#, that uses a Table as a backing store. E.g. the Message module handles reading and writing data from the Message table. Modules are also referenced as {ClassName}. |
| Operation | A method, service, or statement executed by a Module. E.g. the Save method of the Message module saves message data to the Message table. |
| Method | An operation implemented in a module (C# code). E.g. Message/Send emails a message. |
| Service | An operation implemented in a plugin (.NET code). E.g. Attachment/ToPdf converts a document to PDF. |
| Statement | A database query. E.g. the Insert statement of the Message module inserts a row into the Message table. |
| Method Signature | The combination of a module and operation. E.g. Message/Save. |
| ID | Identifier for a row in a table. ID can be used interchangeably with {Table}ID; that is Contact/Select?ID=1 is the same as Contact/Select?ContactID=. |
QBO's API rigorously follows naming patterns. However, due to popular demand, there are a handful of exceptions within the UI (and documentation):
| UI Label | Module | Notes |
|---|---|---|
| Workflow | Decision | 'Flow charting' workflow is implemented by our Decision module. |
| Document | Attachment | Document imaging and generation (mail merge) is implemented by our Attachment module. |
| Task | ImportForm | User tasks, including user-defined fields, are implemented by our ImportForm module. |
| Accounting | Ledger | Accounting functionality is implemented by our Ledger module. |
Core QBO classes such as Attachment, Contact, Decision, Ledger, Message, etc., can be bound to any parent object using 'soft' keys comprising a pair of attributes: Object and ObjectID.
For example:
// Bind a message to Contact 1
Message/Save?Subject=Foo&Object=Contact&ObjectID=1
// Bind a message to Ledger 17
Message/Save?Subject=Bar&Object=Ledger&ObjectID=17
// Bind a message to Attachment 22
Message/Save?Subject=Bar&Object=Attachment&ObjectID=22For the SQL geeks:
-- See all messages associated with ContactID 1
SELECT * FROM Message WHERE Object = 'Contact' AND ObjectID = 1
-- See all messages associated with Attachment 22
SELECT * FROM Message WHERE Object = 'Attachment' AND ObjectID = 22QBO usage of the Object / ObjectID attributes is pervasive.
Templates are used to drive the behavior of instances, with a template row paired with instance row.
| Template | Instance | Description |
|---|---|---|
| AttachmentTemplate | Attachment | A document template determines how to perform a mail merge, what IFileObject repository to store the document in, when to archive and expire the document. |
| DecisionTemplate | Decision | A workflow template determines how to implement a flowchart. |
| ImportFileTemplate | ImportFile | An import template determines how to process (import) data uploaded as a file to QBO. |
| ImportFormTemplate | ImportForm | A task template determines how to render a task, including user-defined fields. |
| ProcessTemplate | Process | A process template determines what Workflow to launch, Ledger to create, and Score to calculate when a process is created. |
The ITemplate interface provides functionality for templated classes, such as ProcessTemplate, ImportFormTemplate, AttachmentTemplate, and DecisionTemplate. Each of these classes determines the behavior of an instance class (Process, ImportForm, Attachment, Decision, etc.).
All template classes implement the ITemplate interface, defining:
| Attribute | Description |
|---|---|
| AppliesTo | Name of the parent object the template applies to (e.g. a workflow may apply to a Loan). |
| Repeatable | Defines how often an instance may occur per parent (repeatability). |
| CustomStatusList | Defines whether an instance has a template-driven status list. |
| CustomTypeList | defines whether an instance has a template-driven type list. |
| ExtranetEnabled | Determines if an instance requires need dedicated access control, or inherits from it's parent record. |
The ITemplate.Repeatable attribute defines how often an instance may occur per parent:
- Once: only one instance per parent is allowed
- One active: only one active instance per parent is allowed
- Many: may instances per parent are allowed|
When the GenericObject.SetProperties sees parameters including Object, ObjectID and Template, a SelectByTemplate statement is called, and these statements are designed to pay attention to the template table's Repeatable column and the instance table's 'active' column(s). For instance, ImportForm/SelectByTemplate works as follows:
-
ImportFormTemplate.Repeatable= 0: if anImportFormrow based on the template exists, it is selected ("reused") -
ImportFormTemplate.Repeatable= 1: if anImportFormrow based on the template and with noActualCompletiondate exists, it is selected ("reused") -
ImportFormTemplate.Repeatable= 2: no existingImportFormrow will be selected, effectively creating a new row
The QBO UI leverages API calls using a standard Javascript library. For any given module, we typically implement the following UI components:
| Operation | Description |
|---|---|
| Home | A home page used to search for or otherwise manage all rows of a table. E.g. Message/Home manages all messages, Attachment/Home manages all documents, etc. |
| Summary | A page used to manage a single row of data, it's ancestors and descendants. E.g. Loan/Summary?ID=1 display a Loan, it's parent Property, and children such as Borrowers, Messages, Attachments, Processes and more. The summary page controls what panels are displayed for a given record, including a 'main' panel that calls the Select UI. |
| Select | A panel used to display a single instance of a module. E.g. Contact/Select?ID=1 displays the Contact properties for the row where ContactID=1. |
| Edit | A panel used to edit a single instance of a module. E.g. Contact/Edit?ID=1 enables updating of the Contact properties for the row where ContactID=1. Contact/Edit (without any ID) enabled creating a new Contact row. |
| Search | A panel used to display multiple rows of data from a table. |
| Dashboard | A panel used to display roll-up statistics of data in a table, such as a chart or a pivot table. |
When looking at a Summary page, the source of the data display is generally obvious. For example, when viewing Contact/Summary?ID=1:
- The data at the top of the page is coming from the
Contacttable:FirstName,LastName, etc. - The data in each of the generic panels at the bottom of the page are straight forward:
- The Messages panel is displaying rows from the
Messagetable that apply toContactID=1 - The Documents panel is displaying rows from the
Attachmenttable that apply toContactID=1 - The Workflow panel is displaying rows from the
Decisiontable that apply toContactID=1
- The Messages panel is displaying rows from the
Other modules may have more complex relationships. For example, Loan/Summary?ID=1:
- The data at the top of the page is primarily coming from the
Loantable- The
Addressdata is coming from thePropertytable
- The
- If the record has a parent object, QBO will display a tab for that parent
- E.g.
Loanhas a parentProperty, soLoan/Summarywill include aPropertytab display data from thePropertytable
- E.g.
- The data in each of the generic panels at the bottom of the page are straight forward (see above).
If you're curious about where a particular data point comes from, leverage QBO's API:
-
Loan/Summary?ID=1will render the standard UI, but -
Loan/Summary?ID=1&Output=Xmlwill render all the data associated withLoanID=1-
Output=Jsonif you prefer JSON
-
In the sample
XMLsnippet below, you can see that data is available from theBorrower,Loan, andPropertytables.
<LoanCollection>
<BorrowerItem>
<BorrowerID>43733</BorrowerID>
<Borrower>Bobby Bankrupt</Borrower>
<LoanID>401504</LoanID>
<ContactID>108183</ContactID>
<Contact>Bobby Bankrupt</Contact>
<FirstName>Bobby</FirstName>
<LastName>Bankrupt</LastName>
</BorrowerItem>
<LoanItem>
<LoanID>1</LoanID>
<Loan>123456789</Loan>
<PropertyID>170283</PropertyID>
<Address>30 Woodlawn Avenue</Address>
<City>Waltham</City>
<State>MA</State>
<PostalCode>02451</PostalCode>
<Property>30 Woodlawn Avenue, Waltham, MA 02451</Property>
</LoanItem>
<PropertyItem>
<PropertyID>170283</PropertyID>
<Property>30 Woodlawn Avenue, Waltham, MA 02451</Property>
<Address>30 Woodlawn Avenue</Address>
<City>Waltham</City>
<State>MA</State>
<PostalCode>02451</PostalCode>
<Latitude>42.386653</Latitude>
<Longitude>-71.248708</Longitude>
</PropertyItem>
</LoanCollection>QBO implements a normalized inheritance pattern in the data tier.
The Contact table provides for name, address and latitude/longitude information. Rather than replicate these fields across multiple table and modules, the following tables 'inherit' from Contact by including a ContactID column:
| Table | Description |
|---|---|
| Organization | Organizations represent corporations or similar entities, frequently driving drop down lists. |
| Broker | Brokers perform real estate transactions, with insurance and verification columns. |
| Person | Persons are users in QBO, with login and password columns. |
SELECT *
FROM Organization
INNER JOIN Contact ON Contact.ContactID = Organization.ContactIDThe Process table backing the Process module provides for open and closed dates, outsourcing to external vendors, tracking workflows and accounting. Many tables in QBO inherit from Process.
| Table | Description |
|---|---|
| Auction | Manage the auction of a property. |
| Bankruptcy | Manage a bankruptcy filing. |
| BidsAtSale | Manage bidding for a property at a foreclosure sale. |
| CashForKeys | Manage the process of paying a borrower to move out of a property for cash. |
| Closing | Manage a real estate closing process. |
| Deficiency | Manage a deficiency judgement process. |
| Eviction | Manage the process of evicting people from a property. |
| Foreclosure | Manage the process of foreclosing on a property. |
| Litigation | Manage a court litigation filing. |
| Offers | Manage an offer for the sale of a property. |
| REO | Manage the process of selling a real estate owned property. |
| Title | Manage the process of ordering and receiving title insurance on a property. |
| Valuation | Manage the process of procuring a valuations (appraisal, broker price opinion, automated value) on a property. |
For the data geeks, some sample queries help convey these relationships:
-- See all Bankruptcies that have no ClosedDate
SELECT *
FROM Bankruptcy
INNER JOIN Process ON Process.ProcessID = Bankruptcy.ProcessID
WHERE Process.ClosedDate IS NULL
-- See all Closings with Status = 'Active' in California
SELECT *
FROM Closing
INNER JOIN Process ON Process.ProcessID = Closing.ProcessID
INNER JOIN Property ON Property.PropertyID = Closing.PropertyID
WHERE Process.Status = 'Active'
AND Property.State = 'CA'
-- See all processes outsourced to Acme Lawfirm; this would include Bankruptcy, Foreclosure, Eviction, and other such rows.
SELECT *
FROM Process
INNER JOIN Organization ON Organization.OrganizationID = Process.AssignedOrganizationID
WHERE Organization.Organization = 'Acme Lawfirm'QBO data tables include a trigger to populate a corresponding {Table}History with any changes made to the table. For example:
| Table | History Table |
|---|---|
Attachment |
AttachmentHistory |
Contact |
ContactHistory |
Decision |
DecisionHistory |
| etc... |
This pattern includes:
- A column in the history table matching each table column except for
Created*andUpdated* - A
RevisionDatecolumn timestamped by the trigger to indicate the date the change was made - A
RevisionPersonIDcolumn stamped by the trigger indicating the user causing the change- this requires that the
UserIDbe available viaSET CONTEXT; see standardUPDATEstatements
- this requires that the
History tables provide an audit trail of changes, and are not intended for OLTP queries. They are useful for manual review on a case-by-case basis.
History tables should be purged frequently; 3 months of history is generally recommended but may be modified according to a client's data retention requirements.