Home
#%% md
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:
- A JSON:API for a database
- Business Logic
- A Basic Web App
#%% md
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:
- Open the Notebook
-
File > New to open 2 Terminal windows
- Drag them to arrange your workspace as shown below
- Click the icon on the left panel to view the Table of Contents
- It should appear as shown below
- 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
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:
- Paste the following into the left Terminal window to run the ApiLogicServer CLI:
ApiLogicServer run
- 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...
- An API endpoint for every table in the sample database
- Logic that enforces integrity on updates, and
- A basic web app with list, display and update pages for every table
Let's explore them.
#%% md
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
Your API can be explored with Open API (Swagger). If running locally, you can explore the api at https://localhost:5000, and:
- Click Customer
- Click Get
- Click Try it out
- Click Execute
Under Investigation: we are exploring using proxy, but not working. You need to be running locally to see this screen:
#%% md
In addition to Open API, we can use curl.
- 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
JSON:API also enables client to control retrieval of related data. Let's add `include'.
- 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.
You can also explore support for these required services.
#%% md
Your API also includes services for insert, update and delete. We can use curl to test the update.
- 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"
}}'
- 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
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
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
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
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:
- Choose the Folder icon on the toolbar to the left
- Open the file
ApiLogicServerTutorial/Log - Patch Order Detail.txt
- 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
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:
-
Multi-page: apps include 1 page per table
-
Multi-table: pages include
related_views
for each related child table, and join in parent data -
Favorite fields first: first-displayed field is "name", or
contains
"name" (configurable) -
Predictive joins: favorite field of each parent is shown (product name - not product id)
-
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
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
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
Use Python to customize logic. Let's alter an Employees salary:
- 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\" }}"
- 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
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
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 |