SlideShare a Scribd company logo
1 of 79
Download to read offline
Building Maintainable Applications in Apex 
Jeffrey Kemp 
AUSOUG Perth, November 2014
All artifacts including code are presented for illustration purposes only. Use at your own risk. Test thoroughly in a non-critical environment before use.
Agenda 
Maintainable PL/SQL 
Case Study 
MVC 
TAPIs & XAPIs 
Starting your Next App
3 Controversial Statements 
2 Interesting Quotes 
1 LOTR reference 
Also
Is this your application?
Lots of processes and conditions 
Complex interactions 
Difficult to learn and refactor 
Low reuse 
Challenges
Readable 
Testable 
Maintainability
Maintainability is in the eye of the beholder maintainer. 
Maintainability
“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.” 
- Martin Golding / John F. Woods (?)
“Always code as if the guy who ends up maintaining your code: 
is reasonably smart but has not read it all; 
knows about 50% of the language; and 
will probably present some of your code at a future user group meeting.” 
- J.K.
DRY 
Consistency 
Naming 
Single-purpose 
Assertions 
One exit point 
Techniques
#include "SDL.h" #define $ for(O=9 #define CX M+=(T%3+2*!(!T*t-6)) #define x ,A=4*!T,O=t,W=h=T<3?u(Q?p:D(A+3),D(A),D(A+1)[i]+D(A+2)*g+):K(t),U=V=K(a),o?U=h,W=V:V, #define C 8*-~L #define Z short #define y a(Z)Y[++O] #define B ),a--||( #define _ ),e--||( #define V(I,D,E)(O=a(I)h[r])&&!(A=(D)(V=(1[E+L]<<16)+*i)/O,A-(I)A)?1[E+L]=V-O*(*E=A):H(0) #define i(B,M)B(o){return M;} #define R(O,M,_)(S=L?a(I Z)O:O,N=L?a(I Z)O M(f=a(I Z)_):(O M(f=a(I n)_))) #define T(_)R(r[u(10,L=4,--)],=,_) #define u(a,r,T)16*i[a]+(I Z)(T i[r]) #define a(_)*(_*)& #define L(_)M(W,_,U) #define M(S,F,T)R(r[S],F,r[T]) #define A(_)(i[L=4]+=2,R(_,=,r[u(10,4,- 2+)])) #define c(R,T)(1[u=19,L+T]=(N=a(R)h[r]*(R)*T)>>16,*i=N,G(F(N-(R)N))) #define h(_)(1&(L?a(Z)_:_)>>C-1) #define I unsigned #define n char #define e(_)v(F(40[L(_##=40[E]+),E]&N==S|_ N<_(int)S)) I n t,e,l[80186],*E,m,u,L,a,T,o,r[1<<21],X,*Y,b,Q,R;I Z*i,M,p,q=3;I*localtime(),f,S,kb,h,W,U,c,g,d,V,A;N,O,P=983040,j[5];SDL_Surface*k;i(F,40[E]=!!o)i(z,42[E]=!!o)i(D,r[a(I)E[259+4*o]+O])i(w,i[o] +=~(-2*47[E])*~L)i(v,G(N-S&&1&(40[z((f^=S^N)&16),E]^f>>C-1)))J(){V=61442;$;O-- ;)V+=40[E+O]<<D(25);}i(H,(46[u=76,J(),T(V),T(9[i]),T(M),M(P+18,=,4*o+2),R(M,=,r[4*o]),E]=0))s(o){$;O-- ;)40[E+O]=1&&1<<D(25)&o;}i(BP,(*i+=262*o*z(F((*E&15)>9|42[E])),*E&=15))i(SP,(w(7),R&&--1[i]&&o?R++,Q&&Q++,M--:0))DX(){$,O*=27840;O-- ;)O[(I*)k->pixels]=-!!(1<<7-O%8&r[O/2880*90+O%720/8+(88+952[l]/128*4+O/720%4<<13)]);SDL_Flip(k);}main(BX,nE)n**nE;{9[i=E=r+P]=P>>4;$;q;)j[-- q]=*++nE?open(*nE,32898):0;read(2[a(I)*i=*j?lseek(*j,0,2)>>9:0,j],E+(M=256),P);$;Y=r+16*9[i]+M,Y-r;Q|R||kb&46[E]&&KB)-- 64[T=1[O=32[L=(X=*Y&7)&1,o=X/2&1,l]=0,t=(c=y)&7,a=c/8&7,Y]>>6,g=~-T?y:(n)y,d=BX=y,l],!T*t-6&&T-2?T-1?d=g:0:(d=y),Q&&Q--,R&&R-- x(O=*Y,O=u=D(51),e=D(8),m=D(14)_ O=*Y/2&7,M+=(n)c*(L^(D(m)[E]|D(22)[E]|D(23)[E]^D(24)[E]))_ L=*Y&8,R(K(X)[r],=,c)_ L=e+=3,o=0,a=X x a=m _ T(X[i])_ A(X[i])_ a<2?M(U,+=1-2*a+,P+24),v(f=1),G(S+1-a==1<<C-1),u=u&4?19:57:a-6?CX+2,a- 3||T(9[i]),a&2&&T(M),a&1&&M(P+18,=,U+2),R(M,=,U[r]),u=67:T(h[r])_(W=U B u=m,M-=~L,R(W[r],&,d)B 0 B L(=~)B L(=-),S=0,u=22,F(N>S)B L?c(I Z,i):c(I n,E)B/**/L?c(Z,i):c(n,E)B L?V(I Z,I,i):V(I n,I Z,E)B L?V(Z,int,i):V(n,Z,E))_++e,h=P,d=c,T=3,a=m,M-- _++e,13[W=h,i]=(o|=!L)?(n)d:d,U=P+26,M-=~!o,u=17+(m=a)_(a=m B L(+=),F(N<S)B L(|=)B e(+)B e(-)B L(&=)B L(-=),F(N>S)B L(^=)B L(-),F(N>S)B L(=))_!L?L=a+=8 x L(=):!o?Q=1,R(r[p=m x V],=,h):A(h[r])_ T=a=0,t=6,g=c x M(U,=,W)_(A=h(h[r]),V=m?++M,(n)g:o?31&2[E]:1)&&(a<4?V%=a/2+C,R(A,=,h[r]):0,a&1?R(h[r],>>=,V):R(h[r],<<=,V),a>3?u=19:0,a<5?0:F(S>>V-1&1)B R(h[r],+=,A>>C-V),G(h(N)^F(N&1))B A&=(1<<V)-1,R(h[r],+=,A<<C-V),G(h(N*2)^F(h(N)))B R(h[r],+=(40[E]<<V-1)+,A>>1+C-V),G(h(N)^F(A&1<<C-V))B R(h[r],+=(40[E]<<C-V)+,A<<1+C-V),F(A&1<<V-1),G(h(N)^h(N*2))B G(h(N)^F(h(S<<V-1)))B G(h(S))B 0 B V<C||F(A),G(0),R(h[r],+=,A*=~((1<<C)- 1>>V)))_(V=!!--1[a=X,i]B V&=!m[E]B V&=m[E]B 0 B V=!++1[i]),M+=V*(n)c _ M+=3-o,L?0:o?9[M=0,i]=BX:T(M),M+=o*L?(n)c:c _ M(U,&,W)_ L=e+=8,W=P,U=K(X)_!R||1[i]?M(m<2?u(8,7,):P,=,m&1?P:u(Q?p:11,6,)),m&1||w(6),m&2||SP(1):0 _!R||1[i]?M(m?P:u(Q?p:11,6,),- ,u(8,7,)),43[u=92,E]=!N,F(N>S),m||w(6),SP(!N==b):0 _ o=L,A(M),m&&A(9[i]),m&2?s(A(V)):o||(4[i]+=c)_ R(U[r],=,d)_ 986[l]^=9,R(*E,=,l[m?2[i]:(n)c])_ R(l[m?2[i]:(n)c],=,*E)_ R=2,b=L,Q&&Q++_ W-U?L(^=),M(U,^=,W),L(^=):0 _ T(m[i])_ A(m[i])_ Q=2,p=m,R&&R++_ L=0,O=*E,F(D(m+=3*42[E]+6*40[E])),z(D(1+m)),N=*E=D(m-1)_ N=BP(m-1)_ 1[E]=-h(*E)_ 2[i]=-h(*i)_ 9[T(9[i]),T(M+5),i]=BX,M=c _ J(),T(V)_ s(A(V))_ J(),s((V&~m)+1[E])_ J(),1[E]=V _ L=o=1 x L(=),M(P+m,=,h+2)_++M,H(3)_ M+=2,H(c&m)_++M,m[E]&&H(4)_(c&=m)?1[E]=*E/c,N=*E%=c:H(0)_*i=N=m&E[L=0]+c*1[E]_*E=-m[E]_*E=r[u(Q?p:m,3,*E+)]_ m[E]^=1 _ E[m/2]=m&1 _ R(*E,&,c)_(a=c B write(1,E,1)B time(j+3),memcpy(r+u(8,3,),localtime(j+3),m)),a<2?*E=~lseek(O=4[E][j],a(I)5[i]<<9,0)?((I(*)())(a?write:read))(O,r+u(8,3,),*i):0:0),O=u,D(16)? v(0):D(17)&&G(F(0)),CX*D(20)+D(18)- D(19)*~!!L,D(15)?O=m=N,41[43[44[E]=h(N),E]=!N,E]=D(50):0,!++q?kb=1,*l?SDL_PumpEvents(),k=k?k:SDL_SetVideoMode(720,348,32,0),DX():k?SDL_Quit() ,k=0:0:0;}i(G,48[E]=o)i(K,P+(L?2*o:2*o+o/4&7))
SELECT a FROM t; 
instead of 
OPEN CURSOR FOR 'select a from t'; 
Quality PL/SQL
max_lines_per_transaction CONSTANT NUMBER := 1000; 
IF line_count > max_lines_per_transaction THEN 
instead of 
IF line_count > 1000 THEN 
Quality PL/SQL
TRANSACTION_PKG.submit 
(tran_id => r.tran_id 
,source_id => NULL 
,description => r.description 
,approved_by => r.updated_by); 
instead of 
TRANSACTION_PKG.submit 
(r.tran_id, NULL, r.description, r.updated_by); 
Quality PL/SQL
Controversial Statement #1
p_this 
l_is 
v_an 
g_awful 
cl_way 
lol_to 
bbq_write 
$!#*_code 
v_no v_more v_hungarian, v_please!
FUNCTION get_name 
(event_id IN events.event_id%TYPE) 
RETURN events.event_name%TYPE IS 
event_name events.event_name%TYPE; 
BEGIN 
IF event_id IS NOT NULL THEN 
SELECT e.event_name INTO event_name 
FROM events e 
WHERE e.event_id = get_name.event_id; 
END IF; 
RETURN event_name; 
END get_name; 
Aliases 
table alias 
function alias
Be consistent. 
More importantly…
FUNCTION get_name 
(event_id IN events.event_id%TYPE) 
RETURN events.event_name%TYPE IS 
event_name events.event_name%TYPE; 
BEGIN 
IF event_id IS NOT NULL THEN 
SELECT e.event_name INTO event_name 
FROM events e 
WHERE e.event_id = get_name.event_id; 
END IF; 
RETURN event_name; 
END get_name; 
Clean code
FUNCTION get_name (event_id IN events.event_id%TYPE) 
RETURN events.event_name%TYPE 
IS event_name events.event_name%TYPE; 
BEGIN 
IF event_id IS NOT NULL THEN 
SELECT events.event_name 
INTO event_name 
FROM events 
WHERE events.event_id = get.event_id; 
END IF; RETURN event_name; 
END get; 
“Ugly” code?
Fun for future maintainers 
PROCEDURE rtrv_evnamev1 
(p_no NUMBER 
,p_nm OUT VARCHAR2 
,p_dt OUT DATE) IS 
BEGIN 
--IF p_id < -100 THEN 
-- g_nm := 'Invalid'; 
--END IF; 
FOR r IN (SELECT event_name, start_date 
FROM events 
WHERE event_id = p_no ) LOOP 
p_nm := r.event_name; 
p_dt := r.start_date; 
END LOOP; 
UPDATE events SET start_date = TRUNC(start_date) WHERE event_id = p_no; 
END;
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')); 
Package names as context
Large government department 
New Apex 4.2 app 
Co-hosted with eBus database instance 
Integrate with Oracle Financials and OBIEE 
Interfaces with other transactional systems 
In-house experience - PL/SQL 
Project Background
Simplified
MVC Architecture
Process: load
Get PK value 
Call TAPI to query record 
Set session state for each column 
load
Validation
Get values from session state into record 
Pass record to TAPI to validate 
Return error message 
validate
process page request
Get v('REQUEST') 
Get values from session state into record 
Pass record to TAPI to insert, update or delete 
process
Parameters? 
v() 
APEX_UTIL.set_session_state() 
(commit issue) 
Apex Logic
logic 
PROCEDURE set_session_state 
(p_name IN VARCHAR2, p_value IN VARCHAR2) AS 
BEGIN 
IF v(p_name) = p_value OR (v(p_name) IS NULL AND p_value IS NULL) THEN 
UPDATE apex_session_data SET item_value = p_value WHERE session_id = nv('SESSION') AND app_id = nv('APP_ID') 
AND item_name = p_name; 
COMMIT; 
END IF; 
END set_session_state; 
DISCLAIMER: this is fictional code!!! WARNING: DO NOT copy & paste!!! 
If the value has changed… 
update it… 
then commit.
PROCEDURE sv 
(p_name IN VARCHAR2 
,p_value IN VARCHAR2 := NULL) AS 
PRAGMA AUTONOMOUS_TRANSACTION; 
BEGIN 
APEX_UTIL.set_session_state 
(p_name => p_name 
,p_value => p_value); 
COMMIT; 
END sv; 
Wrapper for set_session_state
Process a page request 
PROCEDURE p9_process IS 
rv VOLUNTEERS$TAPI.rvtype; 
r VOLUNTEERS$TAPI.rowtype; 
BEGIN 
CASE 
WHEN APEX_APPLICATION.g_request IN ('CREATE','SAVE') THEN 
rv := p9_get; 
IF rv.vol_id IS NULL THEN 
r := VOLUNTEERS$TAPI.ins (rv => rv); 
success('Record created.'); 
ELSE 
r := VOLUNTEERS$TAPI.upd (rv => rv); 
success('Record updated.'); 
END IF; 
p9_set(r); 
WHEN APEX_APPLICATION.g_request = 'DELETE' THEN 
VOLUNTEERS$TAPI.del 
(vol_id => nv('P9_VOL_ID')); 
success('Record deleted.'); 
clear_page_cache; 
ELSE 
NULL; 
END CASE; 
END p9_process;
PROCEDURE success (msg IN VARCHAR2) IS 
BEGIN 
IF APEX_APPLICATION.g_print_success_message 
IS NOT NULL THEN 
APEX_APPLICATION.g_print_success_message 
:= APEX_APPLICATION.g_print_success_message || '<br>'; 
END IF; 
APEX_APPLICATION.g_print_success_message 
:= APEX_APPLICATION.g_print_success_message || msg; 
END success; 
Set success message
PROCEDURE clear_page_cache IS 
BEGIN 
APEX_UTIL.clear_page_cache(APEX_APPLICATION.g_flow_id); 
END clear_page_cache; 
clear_page_cache
It’s ok to call v(). 
A lot. 
Controversial Statement #2
get_row 
FUNCTION p9_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 p9_get;
set row 
PROCEDURE p9_set (r IN VOLUNTEERS$TAPI.rowtype) IS 
BEGIN 
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 p9_set;
PKG.proc; 
PL/SQL in Apex
SELECT t.col_a ,t.col_b ,t.col_c FROM my_table t; 
Move joins, select expressions, etc. to a view 
except Apex-specific stuff like generated APEX_ITEMs 
Always alias everything 
SQL in Apex
Templates, Regions, Items, Reports, etc. 
Conditions (simple) 
Branches 
Authorisation Schemes 
Build Options 
Keep in Apex
Pros 
Fast development 
Smaller apex app 
Dependency analysis 
Procedural control 
Refactoring 
Modularity 
Code re-use 
Customisation 
Version control
Misspelled/missing item names 
Mitigation: isolate all apex code in one package 
Enforce naming conventions – e.g. P1_COLUMN_NAME 
Apex Advisor doesn’t check database package code 
Performance? 
Cons
All v() calls at start of proc, once per item 
All sv() calls at end of proc 
Dynamic Actions calling PL/SQL – use parameters 
Replace PL/SQL with Javascript (where possible) 
- maintainable? 
Performance
Validate - only record-level validation 
Cross-record validation – db constraints or XAPI 
Capture DUP_KEY_ON_VALUE and ORA-02292 for unique and referential constraints 
RAISE APPLICATION_ERROR (-20000, 'User-friendly error message'); 
Error Handling
Process: load & process
Encapsulate all DML for a table 
Row-level validation 
Detect lost updates 
Don’t write first cut by hand – generate them all 
(first cut doesn’t have to be 100% perfect) 
TAPIs
Record types 
rowtype, arraytype, validation record 
Functions/Procedures 
ins / upd / del / merge / get as needed 
bulk_ins / bulk_upd / bulk_merge / get_all as needed 
Constants for enumerations 
All parameters are VARCHAR2 or TAPI record/array types 
TAPI contents
Downside: Updateable SQL Reports not easy 
Mitigation: exception to the rule? 
TAPI cons
TAPI example (variation 1) 
PACKAGE XXSPS_PAYMENT_GATEWAYS$TAPI AS 
SUBTYPE rowtype IS xxsps_payment_gateways%ROWTYPE; 
TYPE arraytype IS TABLE OF rowtype INDEX BY BINARY_INTEGER; 
FUNCTION val 
(pg_name IN VARCHAR2 
,pg_notes IN VARCHAR2 
,pg_start_date IN VARCHAR2 := NULL 
,pg_end_date IN VARCHAR2 := NULL 
) RETURN VARCHAR2; 
FUNCTION ins 
(pg_name IN VARCHAR2 
,pg_notes IN VARCHAR2 
,pg_start_date IN VARCHAR2 := NULL 
,pg_end_date IN VARCHAR2 := NULL 
) RETURN rowtype; 
FUNCTION upd 
(pg_id IN NUMBER 
,pg_name IN VARCHAR2 
,pg_notes IN VARCHAR2 
,pg_start_date IN VARCHAR2 := NULL 
,pg_end_date IN VARCHAR2 := NULL 
,version_id IN NUMBER 
) RETURN rowtype; 
PROCEDURE del 
(pg_id IN NUMBER 
,version_id IN NUMBER); 
FUNCTION get (pg_id IN NUMBER) RETURN rowtype; 
FUNCTION get_id (pg_name IN VARCHAR2) RETURN NUMBER; 
FUNCTION get_next (yr_year IN NUMBER := NULL) RETURN NUMBER; 
FUNCTION is_valid (pg_id IN NUMBER) RETURN BOOLEAN; 
END XXSPS_PAYMENT_GATEWAYS$TAPI;
TAPI example (variation 2) 
PACKAGE XXSPS_PAYMENT_GATEWAYS$TAPI AS 
SUBTYPE rowtype IS xxsps_payment_gateways%ROWTYPE; 
TYPE arraytype IS TABLE OF rowtype INDEX BY BINARY_INTEGER; TYPE rvtype IS RECORD (pg_id NUMBER ,pg_name VARCHAR2(4000) ,pg_notes VARCHAR2(4000) ,pg_start_date VARCHAR2(4000) ,pg_end_date VARCHAR2(4000) ,version_id NUMBER); 
FUNCTION val (rv IN rvtype) RETURN VARCHAR2; 
FUNCTION ins (rv IN rvtype) RETURN rowtype; 
FUNCTION upd (rv IN rvtype) RETURN rowtype; 
PROCEDURE del (rv IN rvtype); 
FUNCTION get (pg_id IN NUMBER) RETURN rowtype; 
FUNCTION get_id (rv IN rvtype) RETURN NUMBER; 
FUNCTION get_next (yr_year IN NUMBER := NULL) RETURN NUMBER; 
FUNCTION is_valid (pg_id IN NUMBER) RETURN BOOLEAN; 
END XXSPS_PAYMENT_GATEWAYS$TAPI;
FUNCTION get (pg_id IN NUMBER) RETURN rowtype IS 
r rowtype; 
BEGIN 
IF pg_id IS NOT NULL THEN 
SELECT x.* INTO r 
FROM xxsps_payment_gateways x 
WHERE x.pg_id = get.pg_id; 
END IF; 
RETURN r; 
END get; 
TAPI get
FUNCTION get (pg_id IN NUMBER) RETURN rowtype IS 
r rowtype; 
BEGIN UTIL_PKG.log_start($$PLSQL_UNIT, 'get'); 
IF pg_id IS NOT NULL THEN 
SELECT x.* INTO r 
FROM xxsps_payment_gateways x 
WHERE x.pg_id = get.pg_id; 
END IF; 
UTIL_PKG.log_end(r.pg_id || ',' || r.pg_name); 
RETURN r; 
EXCEPTION 
WHEN NO_DATA_FOUND THEN 
UTIL_PKG.log_end('NO_DATA_FOUND'); 
RAISE; 
WHEN UTIL_PKG.application_error THEN 
UTIL_PKG.log_end('application_error'); 
RAISE; 
WHEN OTHERS THEN 
UTIL_PKG.raise_error(SQLERRM); 
END get; 
Unexpurgated Version
TAPI ins 
FUNCTION ins 
(pg_name IN VARCHAR2 
,pg_notes IN VARCHAR2 
,pg_start_date IN VARCHAR2 := NULL 
,pg_end_date IN VARCHAR2 := NULL 
) RETURN rowtype IS 
r rowtype; 
error_msg VARCHAR2(32767); 
BEGIN 
error_msg := val 
(pg_name => pg_name 
,pg_notes => pg_notes 
,pg_start_date => pg_start_date 
,pg_end_date => pg_end_date); 
IF error_msg IS NOT NULL THEN 
UTIL_PKG.raise_error(error_msg); 
END IF; 
INSERT INTO xxsps_payment_gateways 
(pg_id 
,pg_name 
,pg_notes 
,pg_start_date 
,pg_end_date) 
VALUES(XXSPS_PG_ID_SEQ.NEXTVAL 
,ins.pg_name 
,ins.pg_notes 
,TO_DATE(ins.pg_start_date,UTIL_PKG.DATE_FORMAT) 
,TO_DATE(ins.pg_end_date,UTIL_PKG.DATE_FORMAT)) 
RETURNING 
pg_id 
,... 
INTO r.pg_id 
,r....; 
RETURN r; 
END ins;
FUNCTION val 
(pg_name IN VARCHAR2 
,pg_notes IN VARCHAR2 
,pg_start_date IN VARCHAR2 := NULL 
,pg_end_date IN VARCHAR2 := NULL 
) RETURN VARCHAR2 IS 
buf VARCHAR2(32767); 
BEGIN 
UTIL_PKG.val_not_null (val => pg_name, label => 'Gateway Name', buf => buf); 
UTIL_PKG.val_max_len (val => pg_name, len => 30, label => 'Gateway Name', buf => buf); 
UTIL_PKG.val_max_len (val => pg_notes, len => 300, label => 'Notes', buf => buf); 
UTIL_PKG.val_date_range 
(start_date => pg_start_date 
,end_date => pg_end_date 
,label => 'Gateway' 
,buf => buf); 
RETURN buf; 
END val; 
TAPI val
TAPI upd (page 1 of 2) 
FUNCTION upd 
(pg_id IN NUMBER 
,pg_name IN VARCHAR2 
,pg_notes IN VARCHAR2 
,pg_start_date IN VARCHAR2 := NULL 
,pg_end_date IN VARCHAR2 := NULL 
,version_id IN NUMBER 
) RETURN rowtype IS 
r rowtype; 
error_msg VARCHAR2(32767); 
BEGIN 
UTIL_PKG.assert(pg_id IS NOT NULL ,'pg_id cannot be NULL'); 
UTIL_PKG.assert(version_id IS NOT NULL ,'version_id cannot be NULL'); 
error_msg := val 
(pg_name => pg_name 
,pg_notes => pg_notes 
,pg_start_date => pg_start_date 
,pg_end_date => pg_end_date); 
IF error_msg IS NOT NULL THEN 
UTIL_PKG.raise_error(error_msg); 
END IF;
TAPI upd (page 2 of 2) 
UPDATE xxsps_payment_gateways x 
SET x.pg_name = upd.pg_name 
,x.pg_notes = upd.pg_notes 
,x.pg_start_date = TO_DATE(upd.pg_start_date ,UTIL_PKG.DATE_FORMAT) 
,x.pg_end_date = TO_DATE(upd.pg_end_date ,UTIL_PKG.DATE_FORMAT) 
WHERE x.pg_id = upd.pg_id 
AND x.version_id = upd.version_id 
RETURNING 
pg_id 
,... 
INTO r.pg_id 
,r....; 
IF SQL%NOTFOUND THEN 
RAISE UTIL_PKG.lost_update; 
END IF; 
RETURN r; 
EXCEPTION 
WHEN DUP_VAL_ON_INDEX THEN 
UTIL_PKG.raise_dup_val_on_index; 
WHEN UTIL_PKG.lost_update THEN 
lost_upd (pg_id => pg_id); 
END upd;
PROCEDURE lost_upd (pg_id IN NUMBER) IS 
db_last_updated_by xxsps_payment_gateways.last_updated_by%TYPE; 
db_last_updated_date xxsps_payment_gateways.last_updated_date%TYPE; 
BEGIN 
SELECT x.last_updated_by 
,x.last_updated_date 
INTO lost_upd.db_last_updated_by 
,lost_upd.db_last_updated_date 
FROM xxsps_payment_gateways x 
WHERE x.pg_id = lost_upd.pg_id; 
UTIL_PKG.raise_lost_update_error 
(last_updated_by => db_last_updated_by 
,last_updated_date => db_last_updated_date); 
EXCEPTION 
WHEN NO_DATA_FOUND THEN 
UTIL_PKG.raise_error('LOST_UPDATE_DELETED'); 
END lost_upd; 
Lost update handler
PROCEDURE del 
(pg_id IN NUMBER 
,version_id IN NUMBER) IS 
r rowtype; 
error_msg VARCHAR2(32767); 
BEGIN 
UTIL_PKG.assert(pg_id IS NOT NULL ,'pg_id cannot be NULL'); 
UTIL_PKG.assert(version_id IS NOT NULL ,'version_id cannot be NULL'); 
r := get(pg_id => pg_id); 
-- delete child records 
XXSPS_PAYMENT_GATEWAY_YEA$TAPI.del_pg (pgy_pg_id => r.pg_id); 
TAPI del 
DELETE xxsps_payment_gateways x 
WHERE x.pg_id = del.pg_id 
AND x.version_id = del.version_id; 
IF SQL%NOTFOUND THEN 
RAISE UTIL_PKG.lost_update; 
END IF; 
EXCEPTION 
WHEN UTIL_PKG.ref_constraint_violation THEN 
UTIL_PKG.raise_del_ref_con_violation; 
WHEN UTIL_PKG.lost_update THEN 
lost_upd (pg_id => pg_id); 
END del;
PROCEDURE bulk_ins (arr IN arraytype) IS 
i BINARY_INTEGER; 
error_msg VARCHAR2(32767); 
BEGIN 
i := arr.FIRST; 
LOOP 
EXIT WHEN i IS NULL; 
error_msg := val 
(ftl_ft_id => arr(i).ftl_ft_id 
,ftl_line_no => arr(i).ftl_line_no 
,ftl_line_type => arr(i).ftl_line_type 
,ftl_line_description => arr(i).ftl_line_description 
,ftl_line_amount => arr(i).ftl_line_amount 
,ftl_sch_code => arr(i).ftl_sch_code); 
IF error_msg IS NOT NULL THEN 
UTIL_PKG.raise_error(error_msg); 
END IF; 
i := arr.NEXT(i); 
END LOOP; 
TAPI bulk_ins 
FORALL i IN INDICES OF arr 
INSERT INTO xxsps_funding_trans_lines 
(ftl_ft_id 
,ftl_line_no 
,ftl_line_type 
,ftl_line_description 
,ftl_line_amount 
,ftl_sch_code) 
VALUES (arr(i).ftl_ft_id 
,arr(i).ftl_line_no 
,arr(i).ftl_line_type 
,arr(i).ftl_line_description 
,arr(i).ftl_line_amount 
,arr(i).ftl_sch_code); 
EXCEPTION 
WHEN DUP_VAL_ON_INDEX THEN 
XXSPS_ERROR.raise_dup_val_on_index; 
END bulk_ins;
PROCEDURE bulk_upd 
(rowids IN rowidarray 
,arr IN arraytype) IS 
i BINARY_INTEGER; 
error_msg VARCHAR2(32767); 
lost_upd_index NUMBER; 
BEGIN 
i := arr.FIRST; 
LOOP 
EXIT WHEN i IS NULL; 
error_msg := val 
(ftl_ft_id => arr(i).ftl_ft_id 
,ftl_line_no => arr(i).ftl_line_no 
,ftl_line_type => arr(i).ftl_line_type 
,ftl_line_description => arr(i).ftl_line_description 
,ftl_line_amount => arr(i).ftl_line_amount 
,ftl_sch_code => arr(i).ftl_sch_code 
,ftl_hold_payment_ind => arr(i).ftl_hold_payment_ind); 
IF error_msg IS NOT NULL THEN 
UTIL_PKG.raise_error(error_msg); 
END IF; 
i := arr.NEXT(i); 
END LOOP; 
TAPI bulk_upd 
FORALL i IN INDICES OF arr 
UPDATE xxsps_funding_trans_lines 
SET ftl_line_type = arr(i).ftl_line_type 
,ftl_line_description = arr(i).ftl_line_description 
,ftl_line_amount = arr(i).ftl_line_amount 
,ftl_sch_code = arr(i).ftl_sch_code 
,ftl_hold_payment_ind = arr(i).ftl_hold_payment_ind 
WHERE ROWID = rowids(i) 
AND version_id = arr(i).version_id; 
FOR i IN 1..arr.COUNT LOOP 
IF SQL%BULK_ROWCOUNT(i) = 0 THEN 
lost_upd_index := i; 
RAISE UTIL_PKG.lost_update; 
END IF; 
END LOOP; 
EXCEPTION 
WHEN DUP_VAL_ON_INDEX THEN 
UTIL_PKG.raise_dup_val_on_index; 
WHEN UTIL_PKG.lost_update THEN 
lost_upd (p_rowid => rowids(lost_upd_index)); 
END bulk_upd;
TAPI_PACKAGE_SPEC CONSTANT VARCHAR2(32767) := q'[ 
CREATE OR REPLACE PACKAGE #TAPI# AS 
SUBTYPE rowtype IS #table#%ROWTYPE; 
TYPE arraytype IS TABLE OF rowtype INDEX BY BINARY_INTEGER; 
TYPE rvtype IS RECORD 
(#REC_COLS#); 
FUNCTION val (rv IN rvtype) RETURN VARCHAR2; 
FUNCTION ins (rv IN rvtype) RETURN rowtype; 
FUNCTION upd (rv IN rvtype) RETURN rowtype; 
PROCEDURE del (rv IN rvtype); 
FUNCTION get (rv IN rvtype) RETURN rowtype; 
END #TAPI#; 
]'; 
TAPI Generator
FUNCTION cols 
(table_name IN VARCHAR2 
,template IN VARCHAR2 
,sep IN VARCHAR2 
) RETURN VARCHAR2 IS 
buf VARCHAR2(32767); 
BEGIN 
FOR r IN ( 
SELECT c.column_name 
FROM user_tab_cols c 
WHERE c.table_name = UPPER(cols.table_name) 
AND c.virtual_column = 'NO' 
AND c.hidden_column = 'NO' 
ORDER BY c.column_id 
) LOOP 
IF buf IS NOT NULL THEN 
buf := buf || CHR(12) || sep; 
END IF; 
buf := buf || REPLACE(template, '#col#', LOWER(r.column_name)); 
END LOOP; 
RETURN buf; 
END cols; 
TAPI Generator
PROCEDURE create_tapi (table_name IN VARCHAR2) IS 
buf CLOB; 
BEGIN 
buf := REPLACE(TAPI_PACKAGE_SPEC, '#TAPI#', SUBSTR(table_name,25) || '$TAPI'); 
buf := REPLACE(TAPI_PACKAGE_SPEC, '#table#', table_name); 
buf := REPLACE(TAPI_PACKAGE_SPEC, '#REC_COLS#' 
,cols(table_name => table_name 
,template => '#col# VARCHAR2(4000)' 
,sep => ',')); 
EXECUTE IMMEDIATE buf; 
-- and then similar for TAPI_PACKAGE_BODY (left as exercise for reader) 
END create_tapi; 
TAPI Generator
Transaction-level validation 
Special cross-table validation 
Not always necessary 
Easy to retrofit later 
Code re-use made easy 
XAPIs
SUBTYPE rowtype IS xxsps_interaction_vw%ROWTYPE; 
PROCEDURE submit_transaction 
(hdr IN rowtype 
,lines IN XXSPS_FUNDING_TRANS_LINES$TAPI.arraytype ) IS 
tran XXSPS_TRANSACTION_PKG.rowtype; 
ftls XXSPS_FUNDING_TRANS_LINES$TAPI.arraytype 
BEGIN 
val (hdr => hdr ,lines => lines); 
tran := XXSPS_TRANSACTION_PKG.get_new (tt_id => hdr.tt_related_tt_id); 
XAPI example 
tran := XXSPS_TRANSACTION_PKG.ins 
(ft_si_id => hdr.si_id 
,ft_tt_id => hdr.tt_related_tt_id 
,ft_description => tran.ft_description 
,ft_yr_year => hdr.si_yr_year 
,ft_pg_id => NVL(hdr.si_pg_id, tran.ft_pg_id) 
,ft_salary_amount => tran.ft_salary_amount 
,ft_cash_amount => tran.ft_cash_amount); 
-- copy the new ID to the child rows 
FOR i IN 1..lines.COUNT LOOP 
ftls(i) := lines(i); 
ftls(i).ftl_ft_id := tran.ft_id; 
END LOOP; 
XXSPS_FUNDING_TRANS_LINES$TAPI.bulk_ins(arr => ftls); 
END submit_interaction;
Steven Feuerstein: “Don’t Repeat SQL Statements” 
Call TAPI functions that return record arrays, ref cursors or use a table function 
Conversely: Tuning a complex, general-purpose query is more difficult than tuning a complex, single-purpose query. 
What about queries?
Database Architecture 
Debug logging & error handling 
Deployment package for DDL 
Unit testing framework 
Code quality standards 
Getting Started
Simple – one primary UI (Apex) 
Database Architecture 
Apex 
data 
integration 
inter- face 
schemas 
interfaces
Shared – multiple UIs 
Database Architecture 
Apex 
Apex 
data 
x 
x 
integration 
inter- face 
schemas 
interfaces
YAGNI. 
Controversial Statement #3
YAGNI? 
“Ref Codes” Table 
Translation Table 
System Parameters
Be Consistent 
Consider Your Successors 
Takeaway
Thank you 
jeffkemponoracle.com

More Related Content

What's hot

Minimization of DFA
Minimization of DFAMinimization of DFA
Minimization of DFAkunj desai
 
Lefmost rightmost TOC.pptx
Lefmost rightmost TOC.pptxLefmost rightmost TOC.pptx
Lefmost rightmost TOC.pptxJisock
 
Theory of automata and formal languages Unit 4
Theory of automata and formal languages Unit 4Theory of automata and formal languages Unit 4
Theory of automata and formal languages Unit 4Abhimanyu Mishra
 
SpaceX Falcon9 Reusable Launch Vehicle
SpaceX Falcon9 Reusable Launch VehicleSpaceX Falcon9 Reusable Launch Vehicle
SpaceX Falcon9 Reusable Launch VehicleAkash Redekar
 
C language (Collected By Dushmanta)
C language  (Collected By Dushmanta)C language  (Collected By Dushmanta)
C language (Collected By Dushmanta)Dushmanta Nath
 
3.5 equivalence of pushdown automata and cfl
3.5 equivalence of pushdown automata and cfl3.5 equivalence of pushdown automata and cfl
3.5 equivalence of pushdown automata and cflSampath Kumar S
 
Introduction to Flight-John D.Anderson.pdf
Introduction to Flight-John D.Anderson.pdfIntroduction to Flight-John D.Anderson.pdf
Introduction to Flight-John D.Anderson.pdfMsMuthumariNITAPAERO
 
Modern C++ Explained: Move Semantics (Feb 2018)
Modern C++ Explained: Move Semantics (Feb 2018)Modern C++ Explained: Move Semantics (Feb 2018)
Modern C++ Explained: Move Semantics (Feb 2018)Olve Maudal
 

What's hot (11)

Minimization of DFA
Minimization of DFAMinimization of DFA
Minimization of DFA
 
C Programming
C ProgrammingC Programming
C Programming
 
Chapter 4 strings
Chapter 4 stringsChapter 4 strings
Chapter 4 strings
 
Circular queues
Circular queuesCircular queues
Circular queues
 
Lefmost rightmost TOC.pptx
Lefmost rightmost TOC.pptxLefmost rightmost TOC.pptx
Lefmost rightmost TOC.pptx
 
Theory of automata and formal languages Unit 4
Theory of automata and formal languages Unit 4Theory of automata and formal languages Unit 4
Theory of automata and formal languages Unit 4
 
SpaceX Falcon9 Reusable Launch Vehicle
SpaceX Falcon9 Reusable Launch VehicleSpaceX Falcon9 Reusable Launch Vehicle
SpaceX Falcon9 Reusable Launch Vehicle
 
C language (Collected By Dushmanta)
C language  (Collected By Dushmanta)C language  (Collected By Dushmanta)
C language (Collected By Dushmanta)
 
3.5 equivalence of pushdown automata and cfl
3.5 equivalence of pushdown automata and cfl3.5 equivalence of pushdown automata and cfl
3.5 equivalence of pushdown automata and cfl
 
Introduction to Flight-John D.Anderson.pdf
Introduction to Flight-John D.Anderson.pdfIntroduction to Flight-John D.Anderson.pdf
Introduction to Flight-John D.Anderson.pdf
 
Modern C++ Explained: Move Semantics (Feb 2018)
Modern C++ Explained: Move Semantics (Feb 2018)Modern C++ Explained: Move Semantics (Feb 2018)
Modern C++ Explained: Move Semantics (Feb 2018)
 

Viewers also liked

Why You Should Use Oracle SQL Developer
Why You Should Use Oracle SQL DeveloperWhy You Should Use Oracle SQL Developer
Why You Should Use Oracle SQL DeveloperJeffrey Kemp
 
Apex and Virtual Private Database
Apex and Virtual Private DatabaseApex and Virtual Private Database
Apex and Virtual Private DatabaseJeffrey Kemp
 
Automate Amazon S3 Storage with Alexandria
Automate Amazon S3 Storage with AlexandriaAutomate Amazon S3 Storage with Alexandria
Automate Amazon S3 Storage with AlexandriaJeffrey Kemp
 
Old Oracle Versions
Old Oracle VersionsOld Oracle Versions
Old Oracle VersionsJeffrey Kemp
 
The APEX QA Plugin
The APEX QA PluginThe APEX QA Plugin
The APEX QA PluginOliver Lemm
 
Aws konferenz vortrag gk
Aws konferenz vortrag gkAws konferenz vortrag gk
Aws konferenz vortrag gkexecupery
 
Učinkovitejše iskanje v Google
Učinkovitejše iskanje v GoogleUčinkovitejše iskanje v Google
Učinkovitejše iskanje v GoogleTomaž Bešter
 
SQLT XPLORE: The SQLT XPLAIN hidden child
SQLT XPLORE: The SQLT XPLAIN hidden childSQLT XPLORE: The SQLT XPLAIN hidden child
SQLT XPLORE: The SQLT XPLAIN hidden childCarlos Sierra
 
SQL Tuning made easier with SQLTXPLAIN (SQLT)
SQL Tuning made easier with SQLTXPLAIN (SQLT)SQL Tuning made easier with SQLTXPLAIN (SQLT)
SQL Tuning made easier with SQLTXPLAIN (SQLT)Carlos Sierra
 
Understanding How is that Adaptive Cursor Sharing (ACS) produces multiple Opt...
Understanding How is that Adaptive Cursor Sharing (ACS) produces multiple Opt...Understanding How is that Adaptive Cursor Sharing (ACS) produces multiple Opt...
Understanding How is that Adaptive Cursor Sharing (ACS) produces multiple Opt...Carlos Sierra
 
Using SQL Plan Management (SPM) to balance Plan Flexibility and Plan Stability
Using SQL Plan Management (SPM) to balance Plan Flexibility and Plan StabilityUsing SQL Plan Management (SPM) to balance Plan Flexibility and Plan Stability
Using SQL Plan Management (SPM) to balance Plan Flexibility and Plan StabilityCarlos Sierra
 
Open Canary - novahackers
Open Canary - novahackersOpen Canary - novahackers
Open Canary - novahackersChris Gates
 

Viewers also liked (14)

Why You Should Use Oracle SQL Developer
Why You Should Use Oracle SQL DeveloperWhy You Should Use Oracle SQL Developer
Why You Should Use Oracle SQL Developer
 
Apex and Virtual Private Database
Apex and Virtual Private DatabaseApex and Virtual Private Database
Apex and Virtual Private Database
 
Automate Amazon S3 Storage with Alexandria
Automate Amazon S3 Storage with AlexandriaAutomate Amazon S3 Storage with Alexandria
Automate Amazon S3 Storage with Alexandria
 
Old Oracle Versions
Old Oracle VersionsOld Oracle Versions
Old Oracle Versions
 
The APEX QA Plugin
The APEX QA PluginThe APEX QA Plugin
The APEX QA Plugin
 
Aws konferenz vortrag gk
Aws konferenz vortrag gkAws konferenz vortrag gk
Aws konferenz vortrag gk
 
Učinkovitejše iskanje v Google
Učinkovitejše iskanje v GoogleUčinkovitejše iskanje v Google
Učinkovitejše iskanje v Google
 
SQLT XPLORE: The SQLT XPLAIN hidden child
SQLT XPLORE: The SQLT XPLAIN hidden childSQLT XPLORE: The SQLT XPLAIN hidden child
SQLT XPLORE: The SQLT XPLAIN hidden child
 
SQL Tuning made easier with SQLTXPLAIN (SQLT)
SQL Tuning made easier with SQLTXPLAIN (SQLT)SQL Tuning made easier with SQLTXPLAIN (SQLT)
SQL Tuning made easier with SQLTXPLAIN (SQLT)
 
Understanding How is that Adaptive Cursor Sharing (ACS) produces multiple Opt...
Understanding How is that Adaptive Cursor Sharing (ACS) produces multiple Opt...Understanding How is that Adaptive Cursor Sharing (ACS) produces multiple Opt...
Understanding How is that Adaptive Cursor Sharing (ACS) produces multiple Opt...
 
Using SQL Plan Management (SPM) to balance Plan Flexibility and Plan Stability
Using SQL Plan Management (SPM) to balance Plan Flexibility and Plan StabilityUsing SQL Plan Management (SPM) to balance Plan Flexibility and Plan Stability
Using SQL Plan Management (SPM) to balance Plan Flexibility and Plan Stability
 
Open Canary - novahackers
Open Canary - novahackersOpen Canary - novahackers
Open Canary - novahackers
 
Single page App
Single page AppSingle page App
Single page App
 
See how technology can simplify your school.
See how technology can simplify your school.See how technology can simplify your school.
See how technology can simplify your school.
 

Similar to Building Maintainable Apps in Apex

Compiler Construction | Lecture 2 | Declarative Syntax Definition
Compiler Construction | Lecture 2 | Declarative Syntax DefinitionCompiler Construction | Lecture 2 | Declarative Syntax Definition
Compiler Construction | Lecture 2 | Declarative Syntax DefinitionEelco Visser
 
CS4200 2019 | Lecture 2 | syntax-definition
CS4200 2019 | Lecture 2 | syntax-definitionCS4200 2019 | Lecture 2 | syntax-definition
CS4200 2019 | Lecture 2 | syntax-definitionEelco Visser
 
C language concept with code apna college.pdf
C language concept with code apna college.pdfC language concept with code apna college.pdf
C language concept with code apna college.pdfmhande899
 
How to transfer bad PLSQL into good (AAAPEKS23)
How to transfer bad PLSQL into good (AAAPEKS23)How to transfer bad PLSQL into good (AAAPEKS23)
How to transfer bad PLSQL into good (AAAPEKS23)Maik Becker
 
Paradigmas de Linguagens de Programacao - Aula #4
Paradigmas de Linguagens de Programacao - Aula #4Paradigmas de Linguagens de Programacao - Aula #4
Paradigmas de Linguagens de Programacao - Aula #4Ismar Silveira
 
Embedded Typesafe Domain Specific Languages for Java
Embedded Typesafe Domain Specific Languages for JavaEmbedded Typesafe Domain Specific Languages for Java
Embedded Typesafe Domain Specific Languages for JavaJevgeni Kabanov
 
iOS 개발자의 Flutter 체험기
iOS 개발자의 Flutter 체험기iOS 개발자의 Flutter 체험기
iOS 개발자의 Flutter 체험기Wanbok Choi
 
Implementing Software Machines in Go and C
Implementing Software Machines in Go and CImplementing Software Machines in Go and C
Implementing Software Machines in Go and CEleanor McHugh
 
Object Orientation vs. Functional Programming in Python
Object Orientation vs. Functional Programming in PythonObject Orientation vs. Functional Programming in Python
Object Orientation vs. Functional Programming in PythonPython Ireland
 
E2 – Fundamentals, Functions & ArraysPlease refer to announcements.docx
E2 – Fundamentals, Functions & ArraysPlease refer to announcements.docxE2 – Fundamentals, Functions & ArraysPlease refer to announcements.docx
E2 – Fundamentals, Functions & ArraysPlease refer to announcements.docxshandicollingwood
 
Developing High Performance Websites and Modern Apps with JavaScript and HTML5
Developing High Performance Websites and Modern Apps with JavaScript and HTML5Developing High Performance Websites and Modern Apps with JavaScript and HTML5
Developing High Performance Websites and Modern Apps with JavaScript and HTML5Doris Chen
 
Clean code _v2003
 Clean code _v2003 Clean code _v2003
Clean code _v2003R696
 
Oracle - Program with PL/SQL - Lession 09
Oracle - Program with PL/SQL - Lession 09Oracle - Program with PL/SQL - Lession 09
Oracle - Program with PL/SQL - Lession 09Thuan Nguyen
 
java compilerCompiler1.javajava compilerCompiler1.javaimport.docx
java compilerCompiler1.javajava compilerCompiler1.javaimport.docxjava compilerCompiler1.javajava compilerCompiler1.javaimport.docx
java compilerCompiler1.javajava compilerCompiler1.javaimport.docxpriestmanmable
 
Groovy Introduction - JAX Germany - 2008
Groovy Introduction - JAX Germany - 2008Groovy Introduction - JAX Germany - 2008
Groovy Introduction - JAX Germany - 2008Guillaume Laforge
 

Similar to Building Maintainable Apps in Apex (20)

Compiler Construction | Lecture 2 | Declarative Syntax Definition
Compiler Construction | Lecture 2 | Declarative Syntax DefinitionCompiler Construction | Lecture 2 | Declarative Syntax Definition
Compiler Construction | Lecture 2 | Declarative Syntax Definition
 
CS4200 2019 | Lecture 2 | syntax-definition
CS4200 2019 | Lecture 2 | syntax-definitionCS4200 2019 | Lecture 2 | syntax-definition
CS4200 2019 | Lecture 2 | syntax-definition
 
C language concept with code apna college.pdf
C language concept with code apna college.pdfC language concept with code apna college.pdf
C language concept with code apna college.pdf
 
How to transfer bad PLSQL into good (AAAPEKS23)
How to transfer bad PLSQL into good (AAAPEKS23)How to transfer bad PLSQL into good (AAAPEKS23)
How to transfer bad PLSQL into good (AAAPEKS23)
 
Vcs16
Vcs16Vcs16
Vcs16
 
Array Cont
Array ContArray Cont
Array Cont
 
Paradigmas de Linguagens de Programacao - Aula #4
Paradigmas de Linguagens de Programacao - Aula #4Paradigmas de Linguagens de Programacao - Aula #4
Paradigmas de Linguagens de Programacao - Aula #4
 
Embedded Typesafe Domain Specific Languages for Java
Embedded Typesafe Domain Specific Languages for JavaEmbedded Typesafe Domain Specific Languages for Java
Embedded Typesafe Domain Specific Languages for Java
 
7 functions
7  functions7  functions
7 functions
 
iOS 개발자의 Flutter 체험기
iOS 개발자의 Flutter 체험기iOS 개발자의 Flutter 체험기
iOS 개발자의 Flutter 체험기
 
pointers 1
pointers 1pointers 1
pointers 1
 
Implementing Software Machines in Go and C
Implementing Software Machines in Go and CImplementing Software Machines in Go and C
Implementing Software Machines in Go and C
 
Object Orientation vs. Functional Programming in Python
Object Orientation vs. Functional Programming in PythonObject Orientation vs. Functional Programming in Python
Object Orientation vs. Functional Programming in Python
 
Functions
FunctionsFunctions
Functions
 
E2 – Fundamentals, Functions & ArraysPlease refer to announcements.docx
E2 – Fundamentals, Functions & ArraysPlease refer to announcements.docxE2 – Fundamentals, Functions & ArraysPlease refer to announcements.docx
E2 – Fundamentals, Functions & ArraysPlease refer to announcements.docx
 
Developing High Performance Websites and Modern Apps with JavaScript and HTML5
Developing High Performance Websites and Modern Apps with JavaScript and HTML5Developing High Performance Websites and Modern Apps with JavaScript and HTML5
Developing High Performance Websites and Modern Apps with JavaScript and HTML5
 
Clean code _v2003
 Clean code _v2003 Clean code _v2003
Clean code _v2003
 
Oracle - Program with PL/SQL - Lession 09
Oracle - Program with PL/SQL - Lession 09Oracle - Program with PL/SQL - Lession 09
Oracle - Program with PL/SQL - Lession 09
 
java compilerCompiler1.javajava compilerCompiler1.javaimport.docx
java compilerCompiler1.javajava compilerCompiler1.javaimport.docxjava compilerCompiler1.javajava compilerCompiler1.javaimport.docx
java compilerCompiler1.javajava compilerCompiler1.javaimport.docx
 
Groovy Introduction - JAX Germany - 2008
Groovy Introduction - JAX Germany - 2008Groovy Introduction - JAX Germany - 2008
Groovy Introduction - JAX Germany - 2008
 

Recently uploaded

Introduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdfIntroduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdfFerryKemperman
 
Post Quantum Cryptography – The Impact on Identity
Post Quantum Cryptography – The Impact on IdentityPost Quantum Cryptography – The Impact on Identity
Post Quantum Cryptography – The Impact on Identityteam-WIBU
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...OnePlan Solutions
 
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...confluent
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsChristian Birchler
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesŁukasz Chruściel
 
Machine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their EngineeringMachine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their EngineeringHironori Washizaki
 
Understanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM ArchitectureUnderstanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM Architecturerahul_net
 
VK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web DevelopmentVK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web Developmentvyaparkranti
 
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024StefanoLambiase
 
Powering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data StreamsPowering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data StreamsSafe Software
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Velvetech LLC
 
MYjobs Presentation Django-based project
MYjobs Presentation Django-based projectMYjobs Presentation Django-based project
MYjobs Presentation Django-based projectAnoyGreter
 
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptx
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptxReal-time Tracking and Monitoring with Cargo Cloud Solutions.pptx
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptxRTS corp
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Matt Ray
 
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Natan Silnitsky
 
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Cizo Technology Services
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesPhilip Schwarz
 
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxUI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxAndreas Kunz
 
Innovate and Collaborate- Harnessing the Power of Open Source Software.pdf
Innovate and Collaborate- Harnessing the Power of Open Source Software.pdfInnovate and Collaborate- Harnessing the Power of Open Source Software.pdf
Innovate and Collaborate- Harnessing the Power of Open Source Software.pdfYashikaSharma391629
 

Recently uploaded (20)

Introduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdfIntroduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdf
 
Post Quantum Cryptography – The Impact on Identity
Post Quantum Cryptography – The Impact on IdentityPost Quantum Cryptography – The Impact on Identity
Post Quantum Cryptography – The Impact on Identity
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
 
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
 
Machine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their EngineeringMachine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their Engineering
 
Understanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM ArchitectureUnderstanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM Architecture
 
VK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web DevelopmentVK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web Development
 
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
 
Powering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data StreamsPowering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data Streams
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...
 
MYjobs Presentation Django-based project
MYjobs Presentation Django-based projectMYjobs Presentation Django-based project
MYjobs Presentation Django-based project
 
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptx
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptxReal-time Tracking and Monitoring with Cargo Cloud Solutions.pptx
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptx
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
 
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
 
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a series
 
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxUI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
 
Innovate and Collaborate- Harnessing the Power of Open Source Software.pdf
Innovate and Collaborate- Harnessing the Power of Open Source Software.pdfInnovate and Collaborate- Harnessing the Power of Open Source Software.pdf
Innovate and Collaborate- Harnessing the Power of Open Source Software.pdf
 

Building Maintainable Apps in Apex

  • 1. Building Maintainable Applications in Apex Jeffrey Kemp AUSOUG Perth, November 2014
  • 2. All artifacts including code are presented for illustration purposes only. Use at your own risk. Test thoroughly in a non-critical environment before use.
  • 3. Agenda Maintainable PL/SQL Case Study MVC TAPIs & XAPIs Starting your Next App
  • 4. 3 Controversial Statements 2 Interesting Quotes 1 LOTR reference Also
  • 5. Is this your application?
  • 6. Lots of processes and conditions Complex interactions Difficult to learn and refactor Low reuse Challenges
  • 8. Maintainability is in the eye of the beholder maintainer. Maintainability
  • 9. “Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.” - Martin Golding / John F. Woods (?)
  • 10. “Always code as if the guy who ends up maintaining your code: is reasonably smart but has not read it all; knows about 50% of the language; and will probably present some of your code at a future user group meeting.” - J.K.
  • 11. DRY Consistency Naming Single-purpose Assertions One exit point Techniques
  • 12. #include "SDL.h" #define $ for(O=9 #define CX M+=(T%3+2*!(!T*t-6)) #define x ,A=4*!T,O=t,W=h=T<3?u(Q?p:D(A+3),D(A),D(A+1)[i]+D(A+2)*g+):K(t),U=V=K(a),o?U=h,W=V:V, #define C 8*-~L #define Z short #define y a(Z)Y[++O] #define B ),a--||( #define _ ),e--||( #define V(I,D,E)(O=a(I)h[r])&&!(A=(D)(V=(1[E+L]<<16)+*i)/O,A-(I)A)?1[E+L]=V-O*(*E=A):H(0) #define i(B,M)B(o){return M;} #define R(O,M,_)(S=L?a(I Z)O:O,N=L?a(I Z)O M(f=a(I Z)_):(O M(f=a(I n)_))) #define T(_)R(r[u(10,L=4,--)],=,_) #define u(a,r,T)16*i[a]+(I Z)(T i[r]) #define a(_)*(_*)& #define L(_)M(W,_,U) #define M(S,F,T)R(r[S],F,r[T]) #define A(_)(i[L=4]+=2,R(_,=,r[u(10,4,- 2+)])) #define c(R,T)(1[u=19,L+T]=(N=a(R)h[r]*(R)*T)>>16,*i=N,G(F(N-(R)N))) #define h(_)(1&(L?a(Z)_:_)>>C-1) #define I unsigned #define n char #define e(_)v(F(40[L(_##=40[E]+),E]&N==S|_ N<_(int)S)) I n t,e,l[80186],*E,m,u,L,a,T,o,r[1<<21],X,*Y,b,Q,R;I Z*i,M,p,q=3;I*localtime(),f,S,kb,h,W,U,c,g,d,V,A;N,O,P=983040,j[5];SDL_Surface*k;i(F,40[E]=!!o)i(z,42[E]=!!o)i(D,r[a(I)E[259+4*o]+O])i(w,i[o] +=~(-2*47[E])*~L)i(v,G(N-S&&1&(40[z((f^=S^N)&16),E]^f>>C-1)))J(){V=61442;$;O-- ;)V+=40[E+O]<<D(25);}i(H,(46[u=76,J(),T(V),T(9[i]),T(M),M(P+18,=,4*o+2),R(M,=,r[4*o]),E]=0))s(o){$;O-- ;)40[E+O]=1&&1<<D(25)&o;}i(BP,(*i+=262*o*z(F((*E&15)>9|42[E])),*E&=15))i(SP,(w(7),R&&--1[i]&&o?R++,Q&&Q++,M--:0))DX(){$,O*=27840;O-- ;)O[(I*)k->pixels]=-!!(1<<7-O%8&r[O/2880*90+O%720/8+(88+952[l]/128*4+O/720%4<<13)]);SDL_Flip(k);}main(BX,nE)n**nE;{9[i=E=r+P]=P>>4;$;q;)j[-- q]=*++nE?open(*nE,32898):0;read(2[a(I)*i=*j?lseek(*j,0,2)>>9:0,j],E+(M=256),P);$;Y=r+16*9[i]+M,Y-r;Q|R||kb&46[E]&&KB)-- 64[T=1[O=32[L=(X=*Y&7)&1,o=X/2&1,l]=0,t=(c=y)&7,a=c/8&7,Y]>>6,g=~-T?y:(n)y,d=BX=y,l],!T*t-6&&T-2?T-1?d=g:0:(d=y),Q&&Q--,R&&R-- x(O=*Y,O=u=D(51),e=D(8),m=D(14)_ O=*Y/2&7,M+=(n)c*(L^(D(m)[E]|D(22)[E]|D(23)[E]^D(24)[E]))_ L=*Y&8,R(K(X)[r],=,c)_ L=e+=3,o=0,a=X x a=m _ T(X[i])_ A(X[i])_ a<2?M(U,+=1-2*a+,P+24),v(f=1),G(S+1-a==1<<C-1),u=u&4?19:57:a-6?CX+2,a- 3||T(9[i]),a&2&&T(M),a&1&&M(P+18,=,U+2),R(M,=,U[r]),u=67:T(h[r])_(W=U B u=m,M-=~L,R(W[r],&,d)B 0 B L(=~)B L(=-),S=0,u=22,F(N>S)B L?c(I Z,i):c(I n,E)B/**/L?c(Z,i):c(n,E)B L?V(I Z,I,i):V(I n,I Z,E)B L?V(Z,int,i):V(n,Z,E))_++e,h=P,d=c,T=3,a=m,M-- _++e,13[W=h,i]=(o|=!L)?(n)d:d,U=P+26,M-=~!o,u=17+(m=a)_(a=m B L(+=),F(N<S)B L(|=)B e(+)B e(-)B L(&=)B L(-=),F(N>S)B L(^=)B L(-),F(N>S)B L(=))_!L?L=a+=8 x L(=):!o?Q=1,R(r[p=m x V],=,h):A(h[r])_ T=a=0,t=6,g=c x M(U,=,W)_(A=h(h[r]),V=m?++M,(n)g:o?31&2[E]:1)&&(a<4?V%=a/2+C,R(A,=,h[r]):0,a&1?R(h[r],>>=,V):R(h[r],<<=,V),a>3?u=19:0,a<5?0:F(S>>V-1&1)B R(h[r],+=,A>>C-V),G(h(N)^F(N&1))B A&=(1<<V)-1,R(h[r],+=,A<<C-V),G(h(N*2)^F(h(N)))B R(h[r],+=(40[E]<<V-1)+,A>>1+C-V),G(h(N)^F(A&1<<C-V))B R(h[r],+=(40[E]<<C-V)+,A<<1+C-V),F(A&1<<V-1),G(h(N)^h(N*2))B G(h(N)^F(h(S<<V-1)))B G(h(S))B 0 B V<C||F(A),G(0),R(h[r],+=,A*=~((1<<C)- 1>>V)))_(V=!!--1[a=X,i]B V&=!m[E]B V&=m[E]B 0 B V=!++1[i]),M+=V*(n)c _ M+=3-o,L?0:o?9[M=0,i]=BX:T(M),M+=o*L?(n)c:c _ M(U,&,W)_ L=e+=8,W=P,U=K(X)_!R||1[i]?M(m<2?u(8,7,):P,=,m&1?P:u(Q?p:11,6,)),m&1||w(6),m&2||SP(1):0 _!R||1[i]?M(m?P:u(Q?p:11,6,),- ,u(8,7,)),43[u=92,E]=!N,F(N>S),m||w(6),SP(!N==b):0 _ o=L,A(M),m&&A(9[i]),m&2?s(A(V)):o||(4[i]+=c)_ R(U[r],=,d)_ 986[l]^=9,R(*E,=,l[m?2[i]:(n)c])_ R(l[m?2[i]:(n)c],=,*E)_ R=2,b=L,Q&&Q++_ W-U?L(^=),M(U,^=,W),L(^=):0 _ T(m[i])_ A(m[i])_ Q=2,p=m,R&&R++_ L=0,O=*E,F(D(m+=3*42[E]+6*40[E])),z(D(1+m)),N=*E=D(m-1)_ N=BP(m-1)_ 1[E]=-h(*E)_ 2[i]=-h(*i)_ 9[T(9[i]),T(M+5),i]=BX,M=c _ J(),T(V)_ s(A(V))_ J(),s((V&~m)+1[E])_ J(),1[E]=V _ L=o=1 x L(=),M(P+m,=,h+2)_++M,H(3)_ M+=2,H(c&m)_++M,m[E]&&H(4)_(c&=m)?1[E]=*E/c,N=*E%=c:H(0)_*i=N=m&E[L=0]+c*1[E]_*E=-m[E]_*E=r[u(Q?p:m,3,*E+)]_ m[E]^=1 _ E[m/2]=m&1 _ R(*E,&,c)_(a=c B write(1,E,1)B time(j+3),memcpy(r+u(8,3,),localtime(j+3),m)),a<2?*E=~lseek(O=4[E][j],a(I)5[i]<<9,0)?((I(*)())(a?write:read))(O,r+u(8,3,),*i):0:0),O=u,D(16)? v(0):D(17)&&G(F(0)),CX*D(20)+D(18)- D(19)*~!!L,D(15)?O=m=N,41[43[44[E]=h(N),E]=!N,E]=D(50):0,!++q?kb=1,*l?SDL_PumpEvents(),k=k?k:SDL_SetVideoMode(720,348,32,0),DX():k?SDL_Quit() ,k=0:0:0;}i(G,48[E]=o)i(K,P+(L?2*o:2*o+o/4&7))
  • 13.
  • 14. SELECT a FROM t; instead of OPEN CURSOR FOR 'select a from t'; Quality PL/SQL
  • 15. max_lines_per_transaction CONSTANT NUMBER := 1000; IF line_count > max_lines_per_transaction THEN instead of IF line_count > 1000 THEN Quality PL/SQL
  • 16. TRANSACTION_PKG.submit (tran_id => r.tran_id ,source_id => NULL ,description => r.description ,approved_by => r.updated_by); instead of TRANSACTION_PKG.submit (r.tran_id, NULL, r.description, r.updated_by); Quality PL/SQL
  • 18. p_this l_is v_an g_awful cl_way lol_to bbq_write $!#*_code v_no v_more v_hungarian, v_please!
  • 19. FUNCTION get_name (event_id IN events.event_id%TYPE) RETURN events.event_name%TYPE IS event_name events.event_name%TYPE; BEGIN IF event_id IS NOT NULL THEN SELECT e.event_name INTO event_name FROM events e WHERE e.event_id = get_name.event_id; END IF; RETURN event_name; END get_name; Aliases table alias function alias
  • 20. Be consistent. More importantly…
  • 21. FUNCTION get_name (event_id IN events.event_id%TYPE) RETURN events.event_name%TYPE IS event_name events.event_name%TYPE; BEGIN IF event_id IS NOT NULL THEN SELECT e.event_name INTO event_name FROM events e WHERE e.event_id = get_name.event_id; END IF; RETURN event_name; END get_name; Clean code
  • 22. FUNCTION get_name (event_id IN events.event_id%TYPE) RETURN events.event_name%TYPE IS event_name events.event_name%TYPE; BEGIN IF event_id IS NOT NULL THEN SELECT events.event_name INTO event_name FROM events WHERE events.event_id = get.event_id; END IF; RETURN event_name; END get; “Ugly” code?
  • 23. Fun for future maintainers PROCEDURE rtrv_evnamev1 (p_no NUMBER ,p_nm OUT VARCHAR2 ,p_dt OUT DATE) IS BEGIN --IF p_id < -100 THEN -- g_nm := 'Invalid'; --END IF; FOR r IN (SELECT event_name, start_date FROM events WHERE event_id = p_no ) LOOP p_nm := r.event_name; p_dt := r.start_date; END LOOP; UPDATE events SET start_date = TRUNC(start_date) WHERE event_id = p_no; END;
  • 24. 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')); Package names as context
  • 25. Large government department New Apex 4.2 app Co-hosted with eBus database instance Integrate with Oracle Financials and OBIEE Interfaces with other transactional systems In-house experience - PL/SQL Project Background
  • 26.
  • 30. Get PK value Call TAPI to query record Set session state for each column load
  • 32. Get values from session state into record Pass record to TAPI to validate Return error message validate
  • 34. Get v('REQUEST') Get values from session state into record Pass record to TAPI to insert, update or delete process
  • 36. logic PROCEDURE set_session_state (p_name IN VARCHAR2, p_value IN VARCHAR2) AS BEGIN IF v(p_name) = p_value OR (v(p_name) IS NULL AND p_value IS NULL) THEN UPDATE apex_session_data SET item_value = p_value WHERE session_id = nv('SESSION') AND app_id = nv('APP_ID') AND item_name = p_name; COMMIT; END IF; END set_session_state; DISCLAIMER: this is fictional code!!! WARNING: DO NOT copy & paste!!! If the value has changed… update it… then commit.
  • 37. PROCEDURE sv (p_name IN VARCHAR2 ,p_value IN VARCHAR2 := NULL) AS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN APEX_UTIL.set_session_state (p_name => p_name ,p_value => p_value); COMMIT; END sv; Wrapper for set_session_state
  • 38. Process a page request PROCEDURE p9_process IS rv VOLUNTEERS$TAPI.rvtype; r VOLUNTEERS$TAPI.rowtype; BEGIN CASE WHEN APEX_APPLICATION.g_request IN ('CREATE','SAVE') THEN rv := p9_get; IF rv.vol_id IS NULL THEN r := VOLUNTEERS$TAPI.ins (rv => rv); success('Record created.'); ELSE r := VOLUNTEERS$TAPI.upd (rv => rv); success('Record updated.'); END IF; p9_set(r); WHEN APEX_APPLICATION.g_request = 'DELETE' THEN VOLUNTEERS$TAPI.del (vol_id => nv('P9_VOL_ID')); success('Record deleted.'); clear_page_cache; ELSE NULL; END CASE; END p9_process;
  • 39. PROCEDURE success (msg IN VARCHAR2) IS BEGIN IF APEX_APPLICATION.g_print_success_message IS NOT NULL THEN APEX_APPLICATION.g_print_success_message := APEX_APPLICATION.g_print_success_message || '<br>'; END IF; APEX_APPLICATION.g_print_success_message := APEX_APPLICATION.g_print_success_message || msg; END success; Set success message
  • 40. PROCEDURE clear_page_cache IS BEGIN APEX_UTIL.clear_page_cache(APEX_APPLICATION.g_flow_id); END clear_page_cache; clear_page_cache
  • 41. It’s ok to call v(). A lot. Controversial Statement #2
  • 42. get_row FUNCTION p9_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 p9_get;
  • 43. set row PROCEDURE p9_set (r IN VOLUNTEERS$TAPI.rowtype) IS BEGIN 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 p9_set;
  • 45. SELECT t.col_a ,t.col_b ,t.col_c FROM my_table t; Move joins, select expressions, etc. to a view except Apex-specific stuff like generated APEX_ITEMs Always alias everything SQL in Apex
  • 46. Templates, Regions, Items, Reports, etc. Conditions (simple) Branches Authorisation Schemes Build Options Keep in Apex
  • 47. Pros Fast development Smaller apex app Dependency analysis Procedural control Refactoring Modularity Code re-use Customisation Version control
  • 48. Misspelled/missing item names Mitigation: isolate all apex code in one package Enforce naming conventions – e.g. P1_COLUMN_NAME Apex Advisor doesn’t check database package code Performance? Cons
  • 49. All v() calls at start of proc, once per item All sv() calls at end of proc Dynamic Actions calling PL/SQL – use parameters Replace PL/SQL with Javascript (where possible) - maintainable? Performance
  • 50. Validate - only record-level validation Cross-record validation – db constraints or XAPI Capture DUP_KEY_ON_VALUE and ORA-02292 for unique and referential constraints RAISE APPLICATION_ERROR (-20000, 'User-friendly error message'); Error Handling
  • 51. Process: load & process
  • 52. Encapsulate all DML for a table Row-level validation Detect lost updates Don’t write first cut by hand – generate them all (first cut doesn’t have to be 100% perfect) TAPIs
  • 53. Record types rowtype, arraytype, validation record Functions/Procedures ins / upd / del / merge / get as needed bulk_ins / bulk_upd / bulk_merge / get_all as needed Constants for enumerations All parameters are VARCHAR2 or TAPI record/array types TAPI contents
  • 54. Downside: Updateable SQL Reports not easy Mitigation: exception to the rule? TAPI cons
  • 55. TAPI example (variation 1) PACKAGE XXSPS_PAYMENT_GATEWAYS$TAPI AS SUBTYPE rowtype IS xxsps_payment_gateways%ROWTYPE; TYPE arraytype IS TABLE OF rowtype INDEX BY BINARY_INTEGER; FUNCTION val (pg_name IN VARCHAR2 ,pg_notes IN VARCHAR2 ,pg_start_date IN VARCHAR2 := NULL ,pg_end_date IN VARCHAR2 := NULL ) RETURN VARCHAR2; FUNCTION ins (pg_name IN VARCHAR2 ,pg_notes IN VARCHAR2 ,pg_start_date IN VARCHAR2 := NULL ,pg_end_date IN VARCHAR2 := NULL ) RETURN rowtype; FUNCTION upd (pg_id IN NUMBER ,pg_name IN VARCHAR2 ,pg_notes IN VARCHAR2 ,pg_start_date IN VARCHAR2 := NULL ,pg_end_date IN VARCHAR2 := NULL ,version_id IN NUMBER ) RETURN rowtype; PROCEDURE del (pg_id IN NUMBER ,version_id IN NUMBER); FUNCTION get (pg_id IN NUMBER) RETURN rowtype; FUNCTION get_id (pg_name IN VARCHAR2) RETURN NUMBER; FUNCTION get_next (yr_year IN NUMBER := NULL) RETURN NUMBER; FUNCTION is_valid (pg_id IN NUMBER) RETURN BOOLEAN; END XXSPS_PAYMENT_GATEWAYS$TAPI;
  • 56. TAPI example (variation 2) PACKAGE XXSPS_PAYMENT_GATEWAYS$TAPI AS SUBTYPE rowtype IS xxsps_payment_gateways%ROWTYPE; TYPE arraytype IS TABLE OF rowtype INDEX BY BINARY_INTEGER; TYPE rvtype IS RECORD (pg_id NUMBER ,pg_name VARCHAR2(4000) ,pg_notes VARCHAR2(4000) ,pg_start_date VARCHAR2(4000) ,pg_end_date VARCHAR2(4000) ,version_id NUMBER); FUNCTION val (rv IN rvtype) RETURN VARCHAR2; FUNCTION ins (rv IN rvtype) RETURN rowtype; FUNCTION upd (rv IN rvtype) RETURN rowtype; PROCEDURE del (rv IN rvtype); FUNCTION get (pg_id IN NUMBER) RETURN rowtype; FUNCTION get_id (rv IN rvtype) RETURN NUMBER; FUNCTION get_next (yr_year IN NUMBER := NULL) RETURN NUMBER; FUNCTION is_valid (pg_id IN NUMBER) RETURN BOOLEAN; END XXSPS_PAYMENT_GATEWAYS$TAPI;
  • 57. FUNCTION get (pg_id IN NUMBER) RETURN rowtype IS r rowtype; BEGIN IF pg_id IS NOT NULL THEN SELECT x.* INTO r FROM xxsps_payment_gateways x WHERE x.pg_id = get.pg_id; END IF; RETURN r; END get; TAPI get
  • 58. FUNCTION get (pg_id IN NUMBER) RETURN rowtype IS r rowtype; BEGIN UTIL_PKG.log_start($$PLSQL_UNIT, 'get'); IF pg_id IS NOT NULL THEN SELECT x.* INTO r FROM xxsps_payment_gateways x WHERE x.pg_id = get.pg_id; END IF; UTIL_PKG.log_end(r.pg_id || ',' || r.pg_name); RETURN r; EXCEPTION WHEN NO_DATA_FOUND THEN UTIL_PKG.log_end('NO_DATA_FOUND'); RAISE; WHEN UTIL_PKG.application_error THEN UTIL_PKG.log_end('application_error'); RAISE; WHEN OTHERS THEN UTIL_PKG.raise_error(SQLERRM); END get; Unexpurgated Version
  • 59. TAPI ins FUNCTION ins (pg_name IN VARCHAR2 ,pg_notes IN VARCHAR2 ,pg_start_date IN VARCHAR2 := NULL ,pg_end_date IN VARCHAR2 := NULL ) RETURN rowtype IS r rowtype; error_msg VARCHAR2(32767); BEGIN error_msg := val (pg_name => pg_name ,pg_notes => pg_notes ,pg_start_date => pg_start_date ,pg_end_date => pg_end_date); IF error_msg IS NOT NULL THEN UTIL_PKG.raise_error(error_msg); END IF; INSERT INTO xxsps_payment_gateways (pg_id ,pg_name ,pg_notes ,pg_start_date ,pg_end_date) VALUES(XXSPS_PG_ID_SEQ.NEXTVAL ,ins.pg_name ,ins.pg_notes ,TO_DATE(ins.pg_start_date,UTIL_PKG.DATE_FORMAT) ,TO_DATE(ins.pg_end_date,UTIL_PKG.DATE_FORMAT)) RETURNING pg_id ,... INTO r.pg_id ,r....; RETURN r; END ins;
  • 60. FUNCTION val (pg_name IN VARCHAR2 ,pg_notes IN VARCHAR2 ,pg_start_date IN VARCHAR2 := NULL ,pg_end_date IN VARCHAR2 := NULL ) RETURN VARCHAR2 IS buf VARCHAR2(32767); BEGIN UTIL_PKG.val_not_null (val => pg_name, label => 'Gateway Name', buf => buf); UTIL_PKG.val_max_len (val => pg_name, len => 30, label => 'Gateway Name', buf => buf); UTIL_PKG.val_max_len (val => pg_notes, len => 300, label => 'Notes', buf => buf); UTIL_PKG.val_date_range (start_date => pg_start_date ,end_date => pg_end_date ,label => 'Gateway' ,buf => buf); RETURN buf; END val; TAPI val
  • 61. TAPI upd (page 1 of 2) FUNCTION upd (pg_id IN NUMBER ,pg_name IN VARCHAR2 ,pg_notes IN VARCHAR2 ,pg_start_date IN VARCHAR2 := NULL ,pg_end_date IN VARCHAR2 := NULL ,version_id IN NUMBER ) RETURN rowtype IS r rowtype; error_msg VARCHAR2(32767); BEGIN UTIL_PKG.assert(pg_id IS NOT NULL ,'pg_id cannot be NULL'); UTIL_PKG.assert(version_id IS NOT NULL ,'version_id cannot be NULL'); error_msg := val (pg_name => pg_name ,pg_notes => pg_notes ,pg_start_date => pg_start_date ,pg_end_date => pg_end_date); IF error_msg IS NOT NULL THEN UTIL_PKG.raise_error(error_msg); END IF;
  • 62. TAPI upd (page 2 of 2) UPDATE xxsps_payment_gateways x SET x.pg_name = upd.pg_name ,x.pg_notes = upd.pg_notes ,x.pg_start_date = TO_DATE(upd.pg_start_date ,UTIL_PKG.DATE_FORMAT) ,x.pg_end_date = TO_DATE(upd.pg_end_date ,UTIL_PKG.DATE_FORMAT) WHERE x.pg_id = upd.pg_id AND x.version_id = upd.version_id RETURNING pg_id ,... INTO r.pg_id ,r....; IF SQL%NOTFOUND THEN RAISE UTIL_PKG.lost_update; END IF; RETURN r; EXCEPTION WHEN DUP_VAL_ON_INDEX THEN UTIL_PKG.raise_dup_val_on_index; WHEN UTIL_PKG.lost_update THEN lost_upd (pg_id => pg_id); END upd;
  • 63. PROCEDURE lost_upd (pg_id IN NUMBER) IS db_last_updated_by xxsps_payment_gateways.last_updated_by%TYPE; db_last_updated_date xxsps_payment_gateways.last_updated_date%TYPE; BEGIN SELECT x.last_updated_by ,x.last_updated_date INTO lost_upd.db_last_updated_by ,lost_upd.db_last_updated_date FROM xxsps_payment_gateways x WHERE x.pg_id = lost_upd.pg_id; UTIL_PKG.raise_lost_update_error (last_updated_by => db_last_updated_by ,last_updated_date => db_last_updated_date); EXCEPTION WHEN NO_DATA_FOUND THEN UTIL_PKG.raise_error('LOST_UPDATE_DELETED'); END lost_upd; Lost update handler
  • 64. PROCEDURE del (pg_id IN NUMBER ,version_id IN NUMBER) IS r rowtype; error_msg VARCHAR2(32767); BEGIN UTIL_PKG.assert(pg_id IS NOT NULL ,'pg_id cannot be NULL'); UTIL_PKG.assert(version_id IS NOT NULL ,'version_id cannot be NULL'); r := get(pg_id => pg_id); -- delete child records XXSPS_PAYMENT_GATEWAY_YEA$TAPI.del_pg (pgy_pg_id => r.pg_id); TAPI del DELETE xxsps_payment_gateways x WHERE x.pg_id = del.pg_id AND x.version_id = del.version_id; IF SQL%NOTFOUND THEN RAISE UTIL_PKG.lost_update; END IF; EXCEPTION WHEN UTIL_PKG.ref_constraint_violation THEN UTIL_PKG.raise_del_ref_con_violation; WHEN UTIL_PKG.lost_update THEN lost_upd (pg_id => pg_id); END del;
  • 65. PROCEDURE bulk_ins (arr IN arraytype) IS i BINARY_INTEGER; error_msg VARCHAR2(32767); BEGIN i := arr.FIRST; LOOP EXIT WHEN i IS NULL; error_msg := val (ftl_ft_id => arr(i).ftl_ft_id ,ftl_line_no => arr(i).ftl_line_no ,ftl_line_type => arr(i).ftl_line_type ,ftl_line_description => arr(i).ftl_line_description ,ftl_line_amount => arr(i).ftl_line_amount ,ftl_sch_code => arr(i).ftl_sch_code); IF error_msg IS NOT NULL THEN UTIL_PKG.raise_error(error_msg); END IF; i := arr.NEXT(i); END LOOP; TAPI bulk_ins FORALL i IN INDICES OF arr INSERT INTO xxsps_funding_trans_lines (ftl_ft_id ,ftl_line_no ,ftl_line_type ,ftl_line_description ,ftl_line_amount ,ftl_sch_code) VALUES (arr(i).ftl_ft_id ,arr(i).ftl_line_no ,arr(i).ftl_line_type ,arr(i).ftl_line_description ,arr(i).ftl_line_amount ,arr(i).ftl_sch_code); EXCEPTION WHEN DUP_VAL_ON_INDEX THEN XXSPS_ERROR.raise_dup_val_on_index; END bulk_ins;
  • 66. PROCEDURE bulk_upd (rowids IN rowidarray ,arr IN arraytype) IS i BINARY_INTEGER; error_msg VARCHAR2(32767); lost_upd_index NUMBER; BEGIN i := arr.FIRST; LOOP EXIT WHEN i IS NULL; error_msg := val (ftl_ft_id => arr(i).ftl_ft_id ,ftl_line_no => arr(i).ftl_line_no ,ftl_line_type => arr(i).ftl_line_type ,ftl_line_description => arr(i).ftl_line_description ,ftl_line_amount => arr(i).ftl_line_amount ,ftl_sch_code => arr(i).ftl_sch_code ,ftl_hold_payment_ind => arr(i).ftl_hold_payment_ind); IF error_msg IS NOT NULL THEN UTIL_PKG.raise_error(error_msg); END IF; i := arr.NEXT(i); END LOOP; TAPI bulk_upd FORALL i IN INDICES OF arr UPDATE xxsps_funding_trans_lines SET ftl_line_type = arr(i).ftl_line_type ,ftl_line_description = arr(i).ftl_line_description ,ftl_line_amount = arr(i).ftl_line_amount ,ftl_sch_code = arr(i).ftl_sch_code ,ftl_hold_payment_ind = arr(i).ftl_hold_payment_ind WHERE ROWID = rowids(i) AND version_id = arr(i).version_id; FOR i IN 1..arr.COUNT LOOP IF SQL%BULK_ROWCOUNT(i) = 0 THEN lost_upd_index := i; RAISE UTIL_PKG.lost_update; END IF; END LOOP; EXCEPTION WHEN DUP_VAL_ON_INDEX THEN UTIL_PKG.raise_dup_val_on_index; WHEN UTIL_PKG.lost_update THEN lost_upd (p_rowid => rowids(lost_upd_index)); END bulk_upd;
  • 67. TAPI_PACKAGE_SPEC CONSTANT VARCHAR2(32767) := q'[ CREATE OR REPLACE PACKAGE #TAPI# AS SUBTYPE rowtype IS #table#%ROWTYPE; TYPE arraytype IS TABLE OF rowtype INDEX BY BINARY_INTEGER; TYPE rvtype IS RECORD (#REC_COLS#); FUNCTION val (rv IN rvtype) RETURN VARCHAR2; FUNCTION ins (rv IN rvtype) RETURN rowtype; FUNCTION upd (rv IN rvtype) RETURN rowtype; PROCEDURE del (rv IN rvtype); FUNCTION get (rv IN rvtype) RETURN rowtype; END #TAPI#; ]'; TAPI Generator
  • 68. FUNCTION cols (table_name IN VARCHAR2 ,template IN VARCHAR2 ,sep IN VARCHAR2 ) RETURN VARCHAR2 IS buf VARCHAR2(32767); BEGIN FOR r IN ( SELECT c.column_name FROM user_tab_cols c WHERE c.table_name = UPPER(cols.table_name) AND c.virtual_column = 'NO' AND c.hidden_column = 'NO' ORDER BY c.column_id ) LOOP IF buf IS NOT NULL THEN buf := buf || CHR(12) || sep; END IF; buf := buf || REPLACE(template, '#col#', LOWER(r.column_name)); END LOOP; RETURN buf; END cols; TAPI Generator
  • 69. PROCEDURE create_tapi (table_name IN VARCHAR2) IS buf CLOB; BEGIN buf := REPLACE(TAPI_PACKAGE_SPEC, '#TAPI#', SUBSTR(table_name,25) || '$TAPI'); buf := REPLACE(TAPI_PACKAGE_SPEC, '#table#', table_name); buf := REPLACE(TAPI_PACKAGE_SPEC, '#REC_COLS#' ,cols(table_name => table_name ,template => '#col# VARCHAR2(4000)' ,sep => ',')); EXECUTE IMMEDIATE buf; -- and then similar for TAPI_PACKAGE_BODY (left as exercise for reader) END create_tapi; TAPI Generator
  • 70. Transaction-level validation Special cross-table validation Not always necessary Easy to retrofit later Code re-use made easy XAPIs
  • 71. SUBTYPE rowtype IS xxsps_interaction_vw%ROWTYPE; PROCEDURE submit_transaction (hdr IN rowtype ,lines IN XXSPS_FUNDING_TRANS_LINES$TAPI.arraytype ) IS tran XXSPS_TRANSACTION_PKG.rowtype; ftls XXSPS_FUNDING_TRANS_LINES$TAPI.arraytype BEGIN val (hdr => hdr ,lines => lines); tran := XXSPS_TRANSACTION_PKG.get_new (tt_id => hdr.tt_related_tt_id); XAPI example tran := XXSPS_TRANSACTION_PKG.ins (ft_si_id => hdr.si_id ,ft_tt_id => hdr.tt_related_tt_id ,ft_description => tran.ft_description ,ft_yr_year => hdr.si_yr_year ,ft_pg_id => NVL(hdr.si_pg_id, tran.ft_pg_id) ,ft_salary_amount => tran.ft_salary_amount ,ft_cash_amount => tran.ft_cash_amount); -- copy the new ID to the child rows FOR i IN 1..lines.COUNT LOOP ftls(i) := lines(i); ftls(i).ftl_ft_id := tran.ft_id; END LOOP; XXSPS_FUNDING_TRANS_LINES$TAPI.bulk_ins(arr => ftls); END submit_interaction;
  • 72. Steven Feuerstein: “Don’t Repeat SQL Statements” Call TAPI functions that return record arrays, ref cursors or use a table function Conversely: Tuning a complex, general-purpose query is more difficult than tuning a complex, single-purpose query. What about queries?
  • 73. Database Architecture Debug logging & error handling Deployment package for DDL Unit testing framework Code quality standards Getting Started
  • 74. Simple – one primary UI (Apex) Database Architecture Apex data integration inter- face schemas interfaces
  • 75. Shared – multiple UIs Database Architecture Apex Apex data x x integration inter- face schemas interfaces
  • 77. YAGNI? “Ref Codes” Table Translation Table System Parameters
  • 78. Be Consistent Consider Your Successors Takeaway