Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Building Maintainable Applications in Apex

3,156 views

Published on

Presented at the Australian Oracle User Group conference on 6 November 2014

Published in: Software
  • Be the first to comment

Building Maintainable Applications in Apex

  1. 1. Building Maintainable Applications in Apex Jeffrey Kemp AUSOUG Perth, November 2014
  2. 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. 3. Agenda Maintainable PL/SQL Case Study MVC TAPIs & XAPIs Starting your Next App
  4. 4. 3 Controversial Statements 2 Interesting Quotes 1 LOTR reference Also
  5. 5. Is this your application?
  6. 6. Lots of processes and conditions Complex interactions Difficult to learn and refactor Low reuse Challenges
  7. 7. Readable Testable Maintainability
  8. 8. Maintainability is in the eye of the beholder maintainer. Maintainability
  9. 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. 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. 11. DRY Consistency Naming Single-purpose Assertions One exit point Techniques
  12. 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. 13. SELECT a FROM t; instead of OPEN CURSOR FOR 'select a from t'; Quality PL/SQL
  14. 14. 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
  15. 15. 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
  16. 16. Controversial Statement #1
  17. 17. p_this l_is v_an g_awful cl_way lol_to bbq_write $!#*_code v_no v_more v_hungarian, v_please!
  18. 18. 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
  19. 19. Be consistent. More importantly…
  20. 20. 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
  21. 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 events.event_name INTO event_name FROM events WHERE events.event_id = get.event_id; END IF; RETURN event_name; END get; “Ugly” code?
  22. 22. 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;
  23. 23. 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
  24. 24. 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
  25. 25. Simplified
  26. 26. MVC Architecture
  27. 27. Process: load
  28. 28. Get PK value Call TAPI to query record Set session state for each column load
  29. 29. Validation
  30. 30. Get values from session state into record Pass record to TAPI to validate Return error message validate
  31. 31. process page request
  32. 32. Get v('REQUEST') Get values from session state into record Pass record to TAPI to insert, update or delete process
  33. 33. Parameters? v() APEX_UTIL.set_session_state() (commit issue) Apex Logic
  34. 34. 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.
  35. 35. 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
  36. 36. 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;
  37. 37. 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
  38. 38. PROCEDURE clear_page_cache IS BEGIN APEX_UTIL.clear_page_cache(APEX_APPLICATION.g_flow_id); END clear_page_cache; clear_page_cache
  39. 39. It’s ok to call v(). A lot. Controversial Statement #2
  40. 40. 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;
  41. 41. 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;
  42. 42. PKG.proc; PL/SQL in Apex
  43. 43. 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
  44. 44. Templates, Regions, Items, Reports, etc. Conditions (simple) Branches Authorisation Schemes Build Options Keep in Apex
  45. 45. Pros Fast development Smaller apex app Dependency analysis Procedural control Refactoring Modularity Code re-use Customisation Version control
  46. 46. 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
  47. 47. 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
  48. 48. 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
  49. 49. Process: load & process
  50. 50. 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
  51. 51. 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
  52. 52. Downside: Updateable SQL Reports not easy Mitigation: exception to the rule? TAPI cons
  53. 53. 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;
  54. 54. 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;
  55. 55. 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
  56. 56. 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
  57. 57. 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;
  58. 58. 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
  59. 59. 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;
  60. 60. 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;
  61. 61. 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
  62. 62. 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;
  63. 63. 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;
  64. 64. 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;
  65. 65. 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
  66. 66. 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
  67. 67. 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
  68. 68. Transaction-level validation Special cross-table validation Not always necessary Easy to retrofit later Code re-use made easy XAPIs
  69. 69. 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;
  70. 70. 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?
  71. 71. Database Architecture Debug logging & error handling Deployment package for DDL Unit testing framework Code quality standards Getting Started
  72. 72. Simple – one primary UI (Apex) Database Architecture Apex data integration inter- face schemas interfaces
  73. 73. Shared – multiple UIs Database Architecture Apex Apex data x x integration inter- face schemas interfaces
  74. 74. YAGNI. Controversial Statement #3
  75. 75. YAGNI? “Ref Codes” Table Translation Table System Parameters
  76. 76. Be Consistent Consider Your Successors Takeaway
  77. 77. Thank you jeffkemponoracle.com

×