Chapter 29 · Production Capstone: Architect, Secure, Tune, Protect, and Operate Oracle
Implement Schema, PL/SQL APIs, Partitioning/Indexes, Security, Services, and Change Automation
Build the capstone relational/partitioned schema and PL/SQL API with least privilege, auditability, encryption/service boundaries and repeatable deployment gates, while keeping licensed or topology-heavy features explicitly optional.
Learning outcomes
The architecture record is useless if deployment scripts create unconstrained tables, grant the runtime user direct DML, and let every application connect to the owner schema. The capstone now turns the design into a repeatable database contract: constraints enforce invariants, partitioning/indexes follow workload evidence, PL/SQL packages expose approved transactions, and auditing/encryption/service controls protect the operational boundary.
Create a ServiceHub capstone schema with relational constraints, partitioned history and workload-aligned indexes.
Expose writes through an invoker-facing PL/SQL package while denying the runtime identity direct table DML.
Create a focused Unified Audit policy and verify records instead of enabling indiscriminate high-volume auditing.
Document TDE/TCPS service security as explicit deployment toggles and current license/keystore requirements.
Make the deployment idempotent/checkable with version and post-deploy acceptance tables rather than ad-hoc console history.
Mandatory examples target Oracle AI Database Free 26ai and were reviewed against RU 23.26.3 (July 2026), SQL Developer 26.2 build 26.2.0.186.2220, and SQLcl 26.2.1.222.1617. Free is limited to 2 CPU cores for processing, 2 GB RAM, 12 GB user data, and one installation per logical environment. The course environment remains CDB/instance FREE, application PDB/service FREEPDB1, owner SERVICEHUB_OWNER, and a local persistent /opt/oracle/oradata learning path. Current 26ai licensing lists Oracle Partitioning, Advanced Security/TDE, Online Table Redefinition, Diagnostics Pack, and Tuning Pack as available in Free; on EE/EE-ES several of these are separately licensed options/packs. Data Guard Redo Apply, RAC, Transaction Guard, and Application Continuity are not available in Free. Mandatory tuning therefore uses core dynamic-performance and cursor-plan evidence so the method remains portable; AWR/ASH/ADDM/Tuning Pack extensions are labeled by offering. Mandatory resilience work uses RMAN validation/backups where safe and rigorous Data Guard/drill simulations where multi-host licensed infrastructure is unavailable. No lab raises COMPATIBLE, changes hidden parameters, applies an RU to Free, or modifies GitHub.
1. Schema is the first line of correctness
BEGIN EXECUTE IMMEDIATE 'DROP USER sh29_app CASCADE';EXCEPTION WHEN OTHERS THEN IF SQLCODE != -1918 THEN RAISE; END IF; END;/BEGIN EXECUTE IMMEDIATE 'DROP PACKAGE servicehub_owner.sh29_work_order_api';EXCEPTION WHEN OTHERS THEN IF SQLCODE != -4043 THEN RAISE; END IF; END;/BEGIN EXECUTE IMMEDIATE 'DROP TABLE servicehub_owner.sh29_work_order_events PURGE';EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;/BEGIN EXECUTE IMMEDIATE 'DROP TABLE servicehub_owner.sh29_work_orders PURGE';EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;/BEGIN EXECUTE IMMEDIATE 'DROP TABLE servicehub_owner.sh29_customers PURGE';EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;/
CREATE TABLE servicehub_owner.sh29_customers ( customer_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, tenant_code VARCHAR2(30) NOT NULL, customer_name VARCHAR2(120) NOT NULL, status_code VARCHAR2(12) DEFAULT 'ACTIVE' NOT NULL, created_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL, CONSTRAINT sh29_customer_tenant_uq UNIQUE(tenant_code,customer_id), CONSTRAINT sh29_customer_status_ck CHECK (status_code IN ('ACTIVE','SUSPENDED')));CREATE TABLE servicehub_owner.sh29_work_orders ( work_order_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, customer_id NUMBER NOT NULL, tenant_code VARCHAR2(30) NOT NULL, status_code VARCHAR2(12) DEFAULT 'OPEN' NOT NULL, priority_code VARCHAR2(10) DEFAULT 'NORMAL' NOT NULL, description VARCHAR2(1000) NOT NULL, amount NUMBER(12,2) DEFAULT 0 NOT NULL, created_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL, updated_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL, CONSTRAINT sh29_wo_customer_fk FOREIGN KEY(tenant_code,customer_id) REFERENCES servicehub_owner.sh29_customers(tenant_code,customer_id), CONSTRAINT sh29_wo_status_ck CHECK (status_code IN ('OPEN','HOLD','CLOSED')), CONSTRAINT sh29_wo_priority_ck CHECK (priority_code IN ('LOW','NORMAL','HIGH')), CONSTRAINT sh29_wo_amount_ck CHECK (amount >= 0));CREATE TABLE servicehub_owner.sh29_work_order_events ( event_id NUMBER GENERATED ALWAYS AS IDENTITY, work_order_id NUMBER NOT NULL, tenant_code VARCHAR2(30) NOT NULL, event_type VARCHAR2(30) NOT NULL, event_ts TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL, payload_json JSON, CONSTRAINT sh29_woe_pk PRIMARY KEY(event_id,event_ts))PARTITION BY RANGE (event_ts)INTERVAL (NUMTOYMINTERVAL(1,'MONTH'))( PARTITION p_before_2026 VALUES LESS THAN (TIMESTAMP '2026-01-01 00:00:00'));
The child foreign key includes tenant_code so a work order cannot silently reference a customer through the wrong tenant identifier. The event history is partitioned by time because retention/purge and time-range reporting are the evidence-backed use cases; partitioning is not added merely because it exists.
2. Index for the OLTP access path you intend to protect
CREATE INDEX servicehub_owner.sh29_wo_tenant_created_ixON servicehub_owner.sh29_work_orders( tenant_code,created_at,work_order_id);CREATE INDEX servicehub_owner.sh29_wo_customer_ixON servicehub_owner.sh29_work_orders( tenant_code,customer_id);CREATE INDEX servicehub_owner.sh29_woe_work_ts_ixON servicehub_owner.sh29_work_order_events( work_order_id,event_ts)LOCAL;
The history index is LOCAL because each index partition aligns with a table partition. The work-order indexes match key access paths from the architecture record. Lesson 3 will verify whether the selective index actually reduces work.
3. Seed a small controlled domain
INSERT INTO servicehub_owner.sh29_customers( tenant_code,customer_name) VALUES('TENANT_A','North Service Group');INSERT INTO servicehub_owner.sh29_customers( tenant_code,customer_name) VALUES('TENANT_B','South Service Group');COMMIT;SELECT customer_id,tenant_code,customer_nameFROM servicehub_owner.sh29_customersORDER BY customer_id;
4. Package the write transaction
CREATE OR REPLACE PACKAGE servicehub_owner.sh29_work_order_apiAUTHID DEFINERAS PROCEDURE create_work_order( p_tenant_code IN VARCHAR2, p_customer_id IN NUMBER, p_description IN VARCHAR2, p_priority_code IN VARCHAR2 DEFAULT 'NORMAL', p_amount IN NUMBER DEFAULT 0, p_work_order_id OUT NUMBER ); PROCEDURE close_work_order( p_tenant_code IN VARCHAR2, p_work_order_id IN NUMBER );END sh29_work_order_api;/
CREATE OR REPLACE PACKAGE BODY servicehub_owner.sh29_work_order_apiAS PROCEDURE create_work_order( p_tenant_code IN VARCHAR2, p_customer_id IN NUMBER, p_description IN VARCHAR2, p_priority_code IN VARCHAR2 DEFAULT 'NORMAL', p_amount IN NUMBER DEFAULT 0, p_work_order_id OUT NUMBER ) AS BEGIN INSERT INTO sh29_work_orders( customer_id,tenant_code,description, priority_code,amount ) VALUES( p_customer_id,p_tenant_code,p_description, p_priority_code,p_amount ) RETURNING work_order_id INTO p_work_order_id; INSERT INTO sh29_work_order_events( work_order_id,tenant_code,event_type,payload_json ) VALUES( p_work_order_id,p_tenant_code,'CREATED', JSON_OBJECT( 'priority' VALUE p_priority_code, 'amount' VALUE p_amount RETURNING JSON ) ); END; PROCEDURE close_work_order( p_tenant_code IN VARCHAR2, p_work_order_id IN NUMBER ) AS BEGIN UPDATE sh29_work_orders SET status_code='CLOSED', updated_at=SYSTIMESTAMP WHERE tenant_code=p_tenant_code AND work_order_id=p_work_order_id AND status_code <> 'CLOSED'; IF SQL%ROWCOUNT = 0 THEN RAISE_APPLICATION_ERROR( -20029,'work order not found or already closed' ); END IF; INSERT INTO sh29_work_order_events( work_order_id,tenant_code,event_type,payload_json ) VALUES( p_work_order_id,p_tenant_code,'CLOSED', JSON_OBJECT('source' VALUE 'SH29_API' RETURNING JSON) ); END;END sh29_work_order_api;/
The package deliberately does not COMMIT. The application owns the transaction boundary from Chapter 27, allowing a larger business transaction to commit/rollback atomically.
5. Least privilege: runtime user executes API, not owner DML
CREATE USER sh29_app NO AUTHENTICATION;GRANT CREATE SESSION TO sh29_app;GRANT EXECUTEON servicehub_owner.sh29_work_order_apiTO sh29_app;GRANT SELECTON servicehub_owner.sh29_customersTO sh29_app;-- For an interactive lab login only:PASSWORD sh29_app-- Set a disposable password interactively; never store it here.
There is no INSERT/UPDATE/DELETE grant on SH29_WORK_ORDERS or SH29_WORK_ORDER_EVENTS. The definer-rights package performs approved owner operations while the caller has only EXECUTE.
6. Deliberate security failure: direct DML should fail
INSERT INTO servicehub_owner.sh29_work_orders( customer_id,tenant_code,description,amount) VALUES( 1,'TENANT_A','Bypass API',10);-- Expected:-- ORA-01031: insufficient privileges-- (or object-not-visible behavior depending on privilege resolution)
If this INSERT succeeds, the least-privilege invariant failed. Repair by revoking direct DML and verify with DBA_TAB_PRIVS.
SELECT grantee,owner,table_name,privilegeFROM dba_tab_privsWHERE grantee='SH29_APP'ORDER BY owner,table_name,privilege;
7. Execute the approved transaction through the package
VARIABLE new_id NUMBERBEGIN servicehub_owner.sh29_work_order_api.create_work_order( p_tenant_code => 'TENANT_A', p_customer_id => 1, p_description => 'Compressor restart inspection', p_priority_code => 'HIGH', p_amount => 125.50, p_work_order_id => :new_id ); COMMIT;END;/PRINT new_id
The foreign key/check constraints still protect the package. A PL/SQL API is not a bypass around relational integrity.
8. Unified audit: audit the high-value API action
BEGIN EXECUTE IMMEDIATE 'NOAUDIT POLICY sh29_api_audit';EXCEPTION WHEN OTHERS THEN NULL;END;/BEGIN EXECUTE IMMEDIATE 'DROP AUDIT POLICY sh29_api_audit';EXCEPTION WHEN OTHERS THEN NULL;END;/CREATE AUDIT POLICY sh29_api_audit ACTIONS EXECUTE ON servicehub_owner.sh29_work_order_api;AUDIT POLICY sh29_api_audit BY sh29_app;
SELECT event_timestamp, dbusername, action_name, object_schema, object_name, return_code, client_program_nameFROM unified_audit_trailWHERE audit_type='Unified Audit' AND dbusername='SH29_APP' AND object_name='SH29_WORK_ORDER_API'ORDER BY event_timestamp DESCFETCH FIRST 20 ROWS ONLY;
Audit records show the database action/identity/result; they do not automatically capture full business intent. Audit only what you can retain/review, archive according to policy, and protect audit access.
9. Encryption and transport are explicit deployment toggles
Current 26ai Free includes Oracle Advanced Security features such as Transparent Data Encryption (TDE). TDE protects database files/backups according to keystore configuration; it does not replace TLS for network transport or application authorization. A production deployment should configure a keystore, key ownership/rotation/recovery and encrypted tablespaces according to policy before moving sensitive data.
SELECT wallet_type, status, wrl_parameter, con_idFROM v$encryption_walletORDER BY con_id;SELECT tablespace_name, encryptedFROM dba_tablespacesORDER BY tablespace_name;
The mandatory lab does not create/rotate a TDE master key because keystore path/backup policy is environment-specific. Chapter 27's TCPS/TLS rules remain the transport requirement.
10. Service naming is a deployable application boundary
Production should expose an application service such as
servicehub_oltp with planned pool/reset/HA
attributes. Standalone PDB service creation uses DBMS_SERVICE;
Clusterware-managed RAC/Data Guard environments use the
supported SRVCTL/broker/service workflow. The mandatory Free lab
keeps using FREEPDB1 and records service identity rather than
mutating a shared built-in service.
SELECT SYS_CONTEXT('USERENV','SERVICE_NAME') AS service_name, SYS_CONTEXT('USERENV','CON_NAME') AS con_nameFROM dual;
11. Repeatable change automation has a schema version and acceptance gates
CREATE TABLE servicehub_owner.sh29_schema_release ( release_id VARCHAR2(30) PRIMARY KEY, applied_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL, applied_by VARCHAR2(128) NOT NULL, artifact_hash VARCHAR2(64) NOT NULL, acceptance_status VARCHAR2(12) NOT NULL CHECK (acceptance_status IN ('PENDING','PASSED','FAILED')));INSERT INTO servicehub_owner.sh29_schema_release( release_id,applied_by,artifact_hash,acceptance_status) VALUES( 'SH29-001', SYS_CONTEXT('USERENV','SESSION_USER'), '6a2f9a7c74db6d88472f615b31b2070f3b630d93d9f1bb5651248713506c47c3', 'PENDING');COMMIT;
The sample SHA-256 value is a deterministic lab identifier; production CI/CD must record the hash of the actual deployment artifact. Production scripts should be version controlled, idempotent where practical, stop on SQL errors, and run post-deploy checks before changing acceptance to PASSED.
12. Post-deploy acceptance
SELECT table_name,partitionedFROM user_tablesWHERE table_name LIKE 'SH29_%'ORDER BY table_name;SELECT index_name,table_name,uniqueness,statusFROM user_indexesWHERE table_name LIKE 'SH29_%'ORDER BY table_name,index_name;SELECT object_name,object_type,statusFROM user_objectsWHERE object_name LIKE 'SH29_%' AND status <> 'VALID';SELECT grantee,table_name,privilegeFROM dba_tab_privsWHERE grantee='SH29_APP'ORDER BY table_name,privilege;
Only after structural, package, privilege and smoke-test checks pass should deployment automation mark the release PASSED.
13. Production judgment
Enforce invariants in schema constraints, expose only the transactions the application needs, and treat partitioning/indexes as workload/retention tools rather than decorations. Use Unified Audit selectively and keep TDE/TLS/secrets as separate controls. The capstone mandatory path needs no paid option in Free, but commercial production must map Partitioning/Advanced Security/audit/management features to the exact offering.
No hidden parameters or restart are required. The table/index/API objects remain for Lessons 3–5. Lesson 3 now measures whether the implemented access paths and transaction behavior actually meet the architecture intent.
Check your understanding
- Why does the package omit COMMIT?
- What should happen when SH29_APP attempts direct table INSERT?
- Why was the event table partitioned by time?
- Does TDE replace TLS?
- What proves a deployment artifact was accepted rather than merely executed?
Review the answers
The caller/application owns the business transaction boundary and can commit/rollback multiple API calls atomically.
It should fail because the runtime user has EXECUTE on the package, not direct DML privileges.
Retention and time-range history access justify a time-based lifecycle boundary.
No. TDE protects data at rest; TLS protects transport.
Post-deploy structural/privilege/application checks recorded against a versioned release artifact.
Authoritative references
- Oracle AI Database Licensing Information — Partitioning/Advanced Security/audit feature availability
- Security Guide — Unified Auditing — custom audit policy lifecycle
- PL/SQL Packages and Types Reference — definer-rights package/API reference
- VLDB and Partitioning Guide — partitioning design and lifecycle
- Advanced Security Guide — TDE/keystore administration