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

Why You Should Use TAPIs

Embed Size (px)

Citation preview

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