Skip to content
Val Huber edited this page Feb 15, 2021 · 1 revision

#%% md

Orientation

There is widespread agreement that APIs are strategic to the business, required for mobile apps and internal / external systems integration.

The problem is that they are time-consuming and costly to develop. This reduces strategic business agility. At the tactical level, UI development can be delayed while awaiting API coding.

API Logic Server provides strategic business agility, by creating an executable server for a database, instantly.

In this 10-20 minute tutorial, we'll create, explore and customize the 3 main elements of an API Logic Server:

  1. A JSON:API for a database
  2. Business Logic
  3. A Basic Web App

#%% md

Configure Workspace

You are in a JupyterLab Notebook. It's a UI for a combination of markdown text and code, installed in a docker container deployed to the cloud, courtesy of MyBinder. What will they think of next.

Installation has already been completed for both Python and ApiLogicServer. ApiLogicServer installs with a Northwind sample database, which we'll be using in this Tutorial.

Arrange your workspace:

  1. Open the Notebook
  2. File > New to open 2 Terminal windows
    • Drag them to arrange your workspace as shown below
  3. Click the icon on the left panel to view the Table of Contents
    • It should appear as shown below
  4. When you're ready, advance:
    • Scroll down, or click the down arrow, or select a section at left.
    • You can click the blue bar to the left to hide a section

If you are considering Jupyter notebooks for your own no-install demos, you can explore how this demo was built on GitHub.

#%% md

Create ApiLogicServer

In this step, we'll use the ApiLogicServer CLI to create and run an api_logic_server project (an API and Web App, backed by underlying Logic):

To create the ApiLogicServer:

  1. Paste the following into the left Terminal window to run the ApiLogicServer CLI:
ApiLogicServer run
  1. Accept the default Database URL parameter to use the supplied sample database. Later, specify a SQLAlchemy url to use your own database.

Key Takeaway: With that 1 command, you have just created...

  1. An API endpoint for every table in the sample database
  2. Logic that enforces integrity on updates, and
  3. A basic web app with list, display and update pages for every table

Let's explore them.

#%% md

1. Explore the API

In many projects, User Interface development is blocked by waiting on API creation.

The run command not only created the project, but it ran python api_logic_server/api_logic_server_run.py to start the server. Let's explore it.

Key Takeaway: ApiLogicServer unblocks UI development with instant API creation.

#%% md

Open API (Swagger)

Your API can be explored with Open API (Swagger). If running locally, you can explore the api at https://localhost:5000, and:

  1. Click Customer
  2. Click Get
  3. Click Try it out
  4. Click Execute

Under Investigation: we are exploring using proxy, but not working. You need to be running locally to see this screen:

#%% md

Get

In addition to Open API, we can use curl.

  1. Paste this into the right terminal window:
curl -X GET "http://localhost:5000/Order\
?include=\
&fields%5BOrder%5D=Id%2CCustomerId%2CEmployeeId%2COrderDate%2CAmountTotal\
&page%5Boffset%5D=0\
&page%5Blimit%5D=2\
&sort=Id%2CCustomerId%2CEmployeeId\
&filter%5BCustomerId%5D=ALFKI"\
 -H  "accept: application/vnd.api+json" \
 -H  "Content-Type: application/vnd.api+json"

Observe:

  • page offset - pagination support
  • fields - with JSON:API, clients can control their API. That's important, since it can reduce network traffic instead of requiring calls on multiple APIs that don't return quite the correct data.

Let's explore some additional aspects of the API.

#%% md

Include Joins

JSON:API also enables client to control retrieval of related data. Let's add `include'.

  1. Paste this into the right Terminal window:
curl -X GET "http://localhost:5000/Order\
?include=OrderDetailList\
&fields%5BOrder%5D=Id%2CCustomerId%2CEmployeeId%2COrderDate%2CAmountTotal\
&page%5Boffset%5D=0\
&page%5Blimit%5D=2\
&sort=Id%2CCustomerId%2CEmployeeId\
&filter%5BCustomerId%5D=ALFKI"\
 -H  "accept: application/vnd.api+json" \
 -H  "Content-Type: application/vnd.api+json"

Observe that response contains "included": [ - the list of related OrderDetail records.

Filtering, Pagination

You can also explore support for these required services.

#%% md

Patch: Logic Enabled Updates

Your API also includes services for insert, update and delete. We can use curl to test the update.

  1. Paste the following into the right Terminal window, and observe the expected failure response:
curl -vX PATCH "http://localhost:5000/Customer/ALFKI/" -H  "accept: application/vnd.api+json" -H  "Content-Type: application/json"  -d '
{
  "data": {
     "attributes": {
        "CreditLimit": "100"
     },
  "type": "Customer",
  "id": "ALFKI"
}}'
  1. This update request violates our predefined rules so it will return an error:
balance (1016.0000000000) exceeds credit (100)

This was entirely intentional, to illustrate the transaction logic underlying the API. Let's explore that.

#%% md

2. Explore Logic

Transaction logic - multi-table derivations, constraints, and actions such as sending mail or messages - is a significant aspect of any database oriented system, as much as half.

Logic is the iceberg under the surface of the API.

#%% md

Cocktail Napkin Explosion

It is striking how a small "cocktail napkin specification" can balloon into hundreds of lines of code:

Implementing logic by conventional procedural code is slow, error prone, and painful to maintain.

#%% md

Logic is Declarative

Api Logic Server dramatically improves conciseness, quality and maintainability, by introducing a signicant innovation for addressing transaction logic: Logic Bank. This approach is based on two things:

  • Rules - 40X more concise using a declarative, spreadsheet-like paradigm, and

  • Python - control and extensibility, using standard tools and techniques

Let's pause for some perspective: what is 40X more concise?

A jetliner flies 4X the speed of Charles Lindbergh's Spirit of St. Louis. This 40 declarative impact is an order of magnitude greater. So, for nearly half your system, you are writing a rule instead of nearly a page of code. See the contrast here.

Note: in this tutorial, the API and Web App were created strictly from the data model. The logic, however, is injected so you can explore it.

Rules are not simply procedural event handlers. They are spreadsheet-like expressions for constraints and derivations applied on update. The "cocktail napkin spec" above is implemented in logic/logic_bank.py, by declaring just 5 rules instead of hundreds of lines of code. Note the rules are simply rigorous expressions of the spec:

This table summarizes the key declarative / procedural differences:

Characteristic Procedural Declarative Why It Matters
Reuse Not Automatic Automatic - all Use Cases 40X Code Reduction
Invocation Passive - only if called Active - call not required Quality
Ordering Manual Automatic Agile Maintenance
Optimizations Manual Automatic Agile Design

Unlike code, you do not call the rules directly. The rules engine in Logic Bank listens for SQLAlchemy before_flush events, and applies the applicable rules in an order that reflects their dependencies. For more information on rules, see the Logic Bank Overview.

The constraint rule (line 52, above) is what caused the update error response above - the Balance exceeded the altered CreditLimit. The transaction is rolled back.

    Rule.constraint(validate=models.Customer,
                    as_condition=lambda row: row.Balance <= row.CreditLimit,
                    error_msg="balance ({row.Balance}) exceeds credit ({row.CreditLimit})")

#%% md

Automatic Re-use

Let's examine the re-use aspect. In traditional procedural coding, re-use is generally achieved with significant design work. But in this declarative approach, logic is automatically re-used for all transactions. That has a significant impact on conciseness, and quality.

Let's see how our logic processes a PATCH to an Order Detail:

curl -X PATCH "http://localhost:5000/OrderDetail/2156/" \
  -H  "accept: application/vnd.api+json" \
  -H  "Content-Type: application/json" \
  -d '
{
  "data":{
    "attributes": {"Quantity": 10},
    "type": "OrderDetail",
    "id": "2156"  
  }
}'

The rule engine generates a log showing each rule that fires, the state of the row, with indenting to show rule chaining. It's in your Terminal console, but easier to see in an editor with disabled Word Wrap:

  1. Choose the Folder icon on the toolbar to the left
  2. Open the file ApiLogicServerTutorial/Log - Patch Order Detail.txt
  3. From the view menu, disable Word Wrap

The rules automated a multi-table rollup transaction:

  • recompute the Order Detail Amount per the formula rule (line 60)
  • adjust the Order AmountTotal (line 58)
  • adjust the Customer Balance (line 55)
  • and check the CreditLimit

Key Takeaway: Logic is 40X more concise, higher quality, and easier to maintain than legacy procedural code.

#%% md

3. Explore the Basic Web App

UI development takes time. That's a problem since:

  • Such effort may not be warranted for admin "back office" screens, and

  • Agile approaches depend on getting working software soon, to drive collaboration and iteration.

ApiLogicServer CLI therefore creates working software now: multi-page, multi-table applications as shown below:

  1. Multi-page: apps include 1 page per table

  2. Multi-table: pages include related_views for each related child table, and join in parent data

  3. Favorite fields first: first-displayed field is "name", or contains "name" (configurable)

  4. Predictive joins: favorite field of each parent is shown (product name - not product id)

  5. Ids last: such boring fields are not shown on lists, and at the end on other pages

If running locally, start the application:

cd ApiLogicServer/api_logic_server
python ui/basic_web_app/run.py

Before running, you must Create Admin Data for Flask App Builder (except for Northwind, which is pre-created).

#%% md

Customize With Python

Recall using API Logic Server CLI:

ApiLogicServer run

created a complete, executable ApiLogicServer in a directory called api_logic_server. On your own machine, you could open the project in an IDE (PyCharm, VSCode, etc) or an Editor (Atom, Text Wrangler, etc). It looks like this in PyCharm:

As noted in the log (in blue, above), ApiLogicServer CLI introspected your database and created a database/models.py file (for SQLAlchemy), and files that declare your api and ui/basic-web-app. You can edit those files to customize your server, as described below.

#%% md

Customize API

Your API is derived from the database, but it is not restricted to that. You can customize your API, e.g., add new endpoints.

For example - an instant API for a database is all well and good - but can we build the "hello world" so desperately sought by enterprises all over the world? We can.

Examine the following code in api_logic_server/api_logic_server_run.py:

@app.route('/hello_world')
def hello_world():  # test it with: http://localhost:5000/hello_world?user=ApiLogicServer
    """
    This is inserted to illustrate that APIs not limited to database objects, but are extensible.

    See: https://github.com/thomaxxl/safrs/wiki/Customization
    """
    user = request.args.get('user')
    return jsonify({"result": f'hello, {user}',
                    "notice": f'add your own endpoints with python'})

You can run this with curl:

curl -X GET "http://localhost:5000/hello_world?user=ApiLogicServer"

#%% md

Customize Logic

Use Python to customize logic. Let's alter an Employees salary:

  1. Paste the following into the right Terminal window:
curl -X PATCH "http://localhost:5000/Employee/1/"\
 -H  "accept: application/vnd.api+json"\
  -H  "Content-Type: application/json" -d "{  \"data\": {\
      \"attributes\": {      \"Salary\": \"90000\"    },\
          \"type\": \"Employee\",    \"id\": \"1\"  }}"
  1. Observe the intentional failure, since Northwind's business policy is that raises must exceed 20% (no doubt your organization has similar policies).

The expected error is implemented in logic/rules_bank.py, where our constraint rule invokes a Python function raise_over_20_percent(). Modern IDEs (e.g., PyCharm) provide code completion and debugging for such logic:

    def raise_over_20_percent(row: models.Employee, old_row: models.Employee, logic_row: LogicRow):
        if logic_row.ins_upd_dlt == "upd" and row.Salary != old_row.Salary:
            return row.Salary >= Decimal('1.20') * old_row.Salary
        else:
            return True

    Rule.constraint(validate=models.Employee,
                    calling=raise_over_20_percent,
                    error_msg="{row.LastName} needs a more meaningful raise")

This illustrates that you can leverage the full power of Python for more complex constraints, as well as other logic such as formulas, events, etc.

Key Takeaway: ApiLogicServer spreadsheet-like logic is extensible with standard Python.

#%% md

Customize Web App

The Basic Web App is driven by ui/basic_web_app/app/views.py, which contains classes like this for each table:

class CustomerModelView(ModelView):
   datamodel = SQLAInterface(Customer)
   list_columns = ["CompanyName", "ContactName", "ContactTitle", "Address", "City"]
   show_columns = ["CompanyName", "ContactName", "ContactTitle", "Address", "City", "Region", "PostalCode", "Country", "Phone", "Fax", "Balance", "CreditLimit", "OrderCount", "Id", "UnpaidOrderCount"]
   edit_columns = ["CompanyName", "ContactName", "ContactTitle", "Address", "City", "Region", "PostalCode", "Country", "Phone", "Fax", "Balance", "CreditLimit", "OrderCount", "Id", "UnpaidOrderCount"]
   add_columns = ["CompanyName", "ContactName", "ContactTitle", "Address", "City", "Region", "PostalCode", "Country", "Phone", "Fax", "Balance", "CreditLimit", "OrderCount", "Id", "UnpaidOrderCount"]
   related_views = [OrderModelView]

appbuilder.add_view(
      CustomerModelView, "Customer List", icon="fa-folder-open-o", category="Menu")

You can edit this file to control what columns are displayed, their display order, and what related data (views) are shown.

Flask AppBuilder has a wide range of capabilities, including charts. For more information, see Flask AppBuilder.

Key Takeaway: ApiLogicServer multi-page, multi-table apps provide instant back-office admin and agile prototyping.

#%% md

ApiLogicServer Tutorial - Wrap-up

In 20 minutes - instead of weeks or months - you have built and executed an ApiLogicServer, and explored its 3 key elements:

Element Key Takeaway Why It Matters
JSON:API Created instantly, customizable Unblock UI Development
Logic Spreadsheet-like rules automate the cocktail napkin spec 40X more concise, customizable with Python
Basic Web App Multi-page, multi-table - created instantly, customizable Back-office admin apps, agile prototyping