Top Banner
Why You Should Use TAPIs Jeffrey Kemp AUSOUG Connect Perth, November 2016
62

Why You Should Use TAPIs

Apr 16, 2017

Download

Technology

Jeffrey Kemp
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Why You Should Use TAPIs

Why You Should Use TAPIs

Jeffrey KempAUSOUG Connect Perth, November

2016

Page 2: Why You Should Use TAPIs

All artifacts including code are presented for illustration purposes only. Use at your own risk. Test thoroughly in a non-critical environment before use.

Page 3: Why You Should Use TAPIs

Main Menu

1. Why a data API?2. Why choose PL/SQL?3. How to structure your API?4. Data API for Apex5. Table APIs (TAPIs)6. Open Source TAPI project

Page 4: Why You Should Use TAPIs

Background

“Building Maintainable Apex Apps”, 2014https://jeffkemponoracle.com/2014/11/14/sample-tapi-apex-application/https://jeffkemponoracle.com/2016/02/11/tapi-generator-mkii/https://jeffkemponoracle.com/2016/02/12/apex-api-call-a-package-for-all-your-dml/https://jeffkemponoracle.com/2016/02/16/apex-api-for-tabular-forms/https://jeffkemponoracle.com/2016/06/30/interactive-grid-apex-5-1-ea/

Page 5: Why You Should Use TAPIs

Why a data API?

Page 6: Why You Should Use TAPIs

Why a data API?

“I’m building a simple Apex app.I’ll just use the built-in processes

to handle all the DML.”

Page 7: Why You Should Use TAPIs

Your requirements get more complex.– More single-row and/or tabular forms

– More pages, more load routines, more validations, more insert/update processes

– Complex conditions– Edge cases, special cases, weird cases

Page 8: Why You Should Use TAPIs
Page 9: Why You Should Use TAPIs

Another system must create the same data – outside of Apex– Re-use validations and processing– Rewrite the validations– Re-engineer all processing (insert/update) logic

– Same edge cases– Different edge cases

Page 10: Why You Should Use TAPIs

Define all validations and processes in one place– Integrated error messages– Works with Apex single-row and tabular forms

Page 11: Why You Should Use TAPIs

Simple wrapper to allow code re-use– Same validations and processes included– Reduced risk of regression– Reduced risk of missing bits

Page 12: Why You Should Use TAPIs

• They get exactly the same logical outcome as we get• No hidden surprises from Apex features

Page 13: Why You Should Use TAPIs

TAPIs

Business Rule ValidationsDefault ValuesReusabilityEncapsulationMaintainability

Page 14: Why You Should Use TAPIs

Maintainability is in the eye of the beholder maintainer.

Page 15: Why You Should Use TAPIs

Techniques

• DRY• Consistency• Naming• Single-purpose• Assertions

Page 16: Why You Should Use TAPIs

Why use PL/SQL for your API?

Page 17: Why You Should Use TAPIs

Why use PL/SQL for your API?

• Data is forever• UIs come and go• Business logic

– tighter coupling with Data than UI

Page 18: Why You Should Use TAPIs

Business Logic

• your schema• your data constraints• your validation rules• your insert/update/delete logic

Page 19: Why You Should Use TAPIs

• keep business logic close to your data

• on Oracle, PL/SQL is the best

Page 20: Why You Should Use TAPIs

Performance

Page 21: Why You Should Use TAPIs

#ThickDB

Page 22: Why You Should Use TAPIs

#ThickDB

Page 23: Why You Should Use TAPIs

How should you structure your API?

Page 24: Why You Should Use TAPIs

How should you structure your API?

Use packages

Page 25: Why You Should Use TAPIs

Focus each PackageFor example:

– “Employees” API– “Departments” API– “Workflow” API– Security (user roles and privileges) API– Apex Utilities

Page 26: Why You Should Use TAPIs

Package names as context

GENERIC_PKG.get_event (event_id => nv('P1_EVENT_ID'));GENERIC_PKG.get_member (member_id => nv('P1_MEMBER_ID'));

EVENT_PKG.get (event_id => nv('P1_EVENT_ID'));MEMBER_PKG.get (member_id => nv('P1_MEMBER_ID'));

Page 27: Why You Should Use TAPIs

Apex processes, simplified

Page 28: Why You Should Use TAPIs

MVC Architecture

entity$APEX

table$TAPI

Page 29: Why You Should Use TAPIs

Process: load

Page 30: Why You Should Use TAPIs

load

1. Get PK value2. Call TAPI to query record3. Set session state for each column

Page 31: Why You Should Use TAPIs

Validation

Page 32: Why You Should Use TAPIs

validate

1. Get values from session state into record2. Pass record to TAPI3. Call APEX_ERROR for each validation error

Page 33: Why You Should Use TAPIs

process page request

Page 34: Why You Should Use TAPIs

process

1. Get v('REQUEST')2. Get values from session state into record3. Pass record to TAPI

Page 35: Why You Should Use TAPIs

Process a page requestprocedure process is rv EVENTS$TAPI.rvtype; r EVENTS$TAPI.rowtype;begin UTIL.check_authorization(SECURITY.Operator);

case when APEX_APPLICATION.g_request = 'CREATE' then rv := apex_get; r := EVENTS$TAPI.ins (rv => rv); apex_set (r => r); UTIL.success('Event created.');

when APEX_APPLICATION.g_request like 'SAVE%' then rv := apex_get; r := EVENTS$TAPI.upd (rv => rv); apex_set (r => r); UTIL.success('Event updated.');

when APEX_APPLICATION.g_request = 'DELETE' then rv := apex_get_pk; EVENTS$TAPI.del (rv => rv); UTIL.clear_page_cache; UTIL.success('Event deleted.');

else null; end case;

end process;

Page 36: Why You Should Use TAPIs

get_rowfunction apex_get return VOLUNTEERS$TAPI.rvtype is rv VOLUNTEERS$TAPI.rvtype;begin

rv.vol_id := nv('P9_VOL_ID'); rv.given_name := v('P9_GIVEN_NAME'); rv.surname := v('P9_SURNAME'); rv.date_of_birth := v('P9_DATE_OF_BIRTH'); rv.address_line := v('P9_ADDRESS_LINE'); rv.suburb := v('P9_SUBURB'); rv.postcode := v('P9_POSTCODE'); rv.state := v('P9_STATE'); rv.home_phone := v('P9_HOME_PHONE'); rv.mobile_phone := v('P9_MOBILE_PHONE'); rv.email_address := v('P9_EMAIL_ADDRESS'); rv.version_id := nv('P9_VERSION_ID');

return rv;end apex_get;

Page 37: Why You Should Use TAPIs

set rowprocedure apex_set (r in VOLUNTEERS$TAPI.rowtype) isbegin

sv('P9_VOL_ID', r.vol_id); sv('P9_GIVEN_NAME', r.given_name); sv('P9_SURNAME', r.surname); sd('P9_DATE_OF_BIRTH', r.date_of_birth); sv('P9_ADDRESS_LINE', r.address_line); sv('P9_STATE', r.state); sv('P9_SUBURB', r.suburb); sv('P9_POSTCODE', r.postcode); sv('P9_HOME_PHONE', r.home_phone); sv('P9_MOBILE_PHONE', r.mobile_phone); sv('P9_EMAIL_ADDRESS', r.email_address); sv('P9_VERSION_ID', r.version_id);

end apex_set;

Page 38: Why You Should Use TAPIs

PL/SQL in Apex

PKG.proc;

Page 39: Why You Should Use TAPIs

SQL in Apexselect t.col_a ,t.col_b ,t.col_cfrom my_table t;

• Move joins, select expressions, etc. to a view– except Apex-specific stuff like generated APEX_ITEMs

Page 40: Why You Should Use TAPIs

Pros• Fast development• Smaller apex app• Dependency analysis• Refactoring

• Modularity• Code re-use• Customisation• Version control

Page 41: Why You Should Use TAPIs

Cons• Misspelled/missing item names

– Mitigation: isolate all apex code in one set of packages

– Enforce naming conventions – e.g. P1_COLUMN_NAME

• Apex Advisor doesn’t check database package code

Page 42: Why You Should Use TAPIs

Apex API Coding Standards

• All v() calls at start of proc, once per item• All sv() calls at end of proc• Constants instead of 'P1_COL'• Dynamic Actions calling PL/SQL – use parameters• Replace PL/SQL with Javascript (where possible)

Page 43: Why You Should Use TAPIs

Error Handling

• Validate - only record-level validation• Cross-record validation – db constraints + XAPI• Capture DUP_KEY_ON_VALUE and ORA-02292 for unique

and referential constraints• APEX_ERROR.add_error

Page 44: Why You Should Use TAPIs

TAPIs

• Encapsulate all DML for a table• Row-level validation• Detect lost updates• Generated

Page 45: Why You Should Use TAPIs

TAPI contents• Record types

– rowtype, arraytype, validation record type• Functions/Procedures

– ins / upd / del / merge / get– bulk_ins / bulk_upd / bulk_merge

• Constants for enumerations

Page 46: Why You Should Use TAPIs

Why not a simple rowtype?procedure ins (emp_name in varchar2 ,dob in date ,salary in number ) isbegin if is_invalid_date (dob) then raise_error('Date of birth bad'); elsif is_invalid_number (salary) then raise_error('Salary bad'); end if; insert into emp (emp_name, dob, salary) values (emp_name, dob, salary);end ins;

ins (emp_name => :P1_EMP_NAME, dob => :P1_DOB, salary => :P1_SALARY);

ORA-01858: a non-numeric character was found where a numeric was expected

It’s too late to validate data types here!

Page 47: Why You Should Use TAPIs

Validation record typetype rv is record ( emp_name varchar2(4000) , dob varchar2(4000) , salary varchar2(4000));

procedure ins (rv in rvtype) isbegin if is_invalid_date (dob) then raise_error('Date of birth bad'); elsif is_invalid_number (salary) then raise_error('Salary bad'); end if; insert into emp (emp_name, dob, salary) values (emp_name, dob, salary);end ins;

ins (emp_name => :P1_EMP_NAME, dob => :P1_DOB, salary => :P1_SALARY);

I’m sorry Dave, I can’t do that - Date of birth bad

Page 48: Why You Should Use TAPIs

Example Tablecreate table venues ( venue_id integer default on null venue_id_seq.nextval , name varchar2(200 char) , map_position varchar2(200 char) , created_dt date default on null sysdate , created_by varchar2(100 char) default on null sys_context('APEX$SESSION','APP_USER') , last_updated_dt date default on null sysdate , last_updated_by varchar2(100 char) default on null sys_context('APEX$SESSION','APP_USER') , version_id integer default on null 1 );

Page 49: Why You Should Use TAPIs

TAPI examplepackage VENUES$TAPI as

cursor cur is select x.* from venues;

subtype rowtype is cur%rowtype;

type arraytype is table of rowtype index by binary_integer;

type rvtype is record (venue_id venues.venue_id%type ,name varchar2(4000) ,map_position varchar2(4000) ,version_id venues.version_id%type );

type rvarraytype is table of rvtype index by binary_integer;

-- validate the rowfunction val (rv IN rvtype) return varchar2;

-- insert a rowfunction ins (rv IN rvtype) return rowtype;

-- update a rowfunction upd (rv IN rvtype) return rowtype;

-- delete a rowprocedure del (rv IN rvtype);

end VENUES$TAPI;

Page 50: Why You Should Use TAPIs

TAPI insfunction ins (rv in rvtype) return rowtype is r rowtype; error_msg varchar2(32767);begin

error_msg := val (rv => rv);

if error_msg is not null then UTIL.raise_error(error_msg); end if;

insert into venues (name ,map_position) values(rv.name ,rv.map_position) returning venue_id ,... into r;

return r;exception when dup_val_on_index then UTIL.raise_dup_val_on_index;end ins;

Page 51: Why You Should Use TAPIs

TAPI valfunction val (rv in rvtype) return varchar2 isbegin

UTIL.val_not_null (val => rv.host_id, column_name => HOST_ID); UTIL.val_not_null (val => rv.event_type, column_name => EVENT_TYPE); UTIL.val_not_null (val => rv.title, column_name => TITLE); UTIL.val_not_null (val => rv.start_dt, column_name => START_DT); UTIL.val_max_len (val => rv.event_type, len => 100, column_name => EVENT_TYPE); UTIL.val_max_len (val => rv.title, len => 100, column_name => TITLE); UTIL.val_max_len (val => rv.description, len => 4000, column_name => DESCRIPTION); UTIL.val_datetime (val => rv.start_dt, column_name => START_DT); UTIL.val_datetime (val => rv.end_dt, column_name => END_DT); UTIL.val_domain (val => rv.repeat ,valid_values => t_str_array(DAILY, WEEKLY, MONTHLY, ANNUALLY) ,column_name => REPEAT); UTIL.val_integer (val => rv.repeat_interval, range_low => 1, column_name => REPEAT_INTERVAL); UTIL.val_date (val => rv.repeat_until, column_name => REPEAT_UNTIL); UTIL.val_ind (val => rv.repeat_ind, column_name => REPEAT_IND);

return UTIL.first_error;end val;

Page 52: Why You Should Use TAPIs

TAPI updfunction upd (rv in rvtype) return rowtype is r rowtype; error_msg varchar2(32767);begin error_msg := val (rv => rv); if error_msg is not null then UTIL.raise_error(error_msg); end if;

update venues x set x.name = rv.name ,x.map_position = rv.map_position where x.venue_id = rv.venue_id and x.version_id = rv.version_id returning venue_id ,... into r;

if sql%notfound then raise UTIL.lost_update; end if;

return r;exception when dup_val_on_index then UTIL.raise_dup_val_on_index; when UTIL.ref_constraint_violation then UTIL.raise_ref_con_violation; when UTIL.lost_update then lost_upd (rv => rv);end upd;

Page 53: Why You Should Use TAPIs

Lost update handlerprocedure lost_upd (rv in rvtype) is db_last_updated_by venues.last_updated_by%type; db_last_updated_dt venues.last_updated_dt%type;begin select x.last_updated_by ,x.last_updated_dt into db_last_updated_by ,db_last_updated_dt from venues x where x.venue_id = rv.venue_id;

UTIL.raise_lost_update (updated_by => db_last_updated_by ,updated_dt => db_last_updated_dt);exception when no_data_found then UTIL.raise_error('LOST_UPDATE_DEL');end lost_upd;

“This record was changed by JOE BLOGGS at 4:31pm. Please refresh the page to see changes.”

“This record was deleted by another user.”

Page 54: Why You Should Use TAPIs

TAPI bulk_insfunction bulk_ins (arr in rvarraytype) return number isbegin bulk_val(arr);

forall i in indices of arr insert into venues (name ,map_position) values (arr(i).name ,arr(i).map_position);

return sql%rowcount;exception when dup_val_on_index then UTIL.raise_dup_val_on_index;end bulk_ins;

Page 55: Why You Should Use TAPIs

What about queries?

Tuning a complex, general-purpose queryis more difficult than

tuning a complex, single-purpose query.

Page 56: Why You Should Use TAPIs

Generating Code

• Only PL/SQL• Templates compiled in the schema• Simple syntax• Sub-templates (“includes”) for extensibility

Page 57: Why You Should Use TAPIs

OraOpenSource TAPI

• Runs on NodeJS• Uses Handlebars for template processing• https://github.com/OraOpenSource/oos-tapi/• Early stages, needs contributors

Page 58: Why You Should Use TAPIs

OOS-TAPI Examplecreate or replace package body {{toLowerCase table_name}} as

gc_scope_prefix constant varchar2(31) := lower($$plsql_unit) || '.';

procedure ins_rec( {{#each columns}} p_{{toLowerCase column_name}} in {{toLowerCase data_type}} {{#unless @last}},{{lineBreak}}{{/unless}} {{~/each}} );

end {{toLowerCase table_name}};

Page 59: Why You Should Use TAPIs

oddgen• SQL*Developer plugin• Code generator, including TAPIs• Support now added in jk64 Apex TAPI generator

https://www.oddgen.org

Page 60: Why You Should Use TAPIs
Page 61: Why You Should Use TAPIs

Takeaways

Be Consistent

Consider Your Successors

Page 62: Why You Should Use TAPIs

Thank you

jeffkemponoracle.com