Skip to content
PYD ENTERPRISES

Migration guide

PL/SQL stored procedures to microservices: lift rules out of the database

Decades of business logic live in Oracle packages and triggers. This guide shows how to find every rule and every hidden write so service boundaries are drawn around real behavior.

Oracle PL/SQLMicroservices
DIFFICULTY
Very high
OUTPUT
OpenAPI / YAML
SYNTAX TRAPS
5 covered
WORKFLOW
6 steps

By Pranav Kulkarni, Head of Language Engineering · Updated 15 Oct 2026

Technical syntax challenges

Where Oracle PL/SQL migrations go wrong.

01

Row-by-row cursor loops

FOR r IN c_overdue LOOP ... UPDATE ... END LOOP applies a rule one row at a time, often with a COMMIT every N rows. The rule is inside the loop; the commit cadence is an operational property that the target service must decide on deliberately, because partial runs leave partial results.

02

Trigger side effects

An AFTER UPDATE ON invoices FOR EACH ROW trigger that writes invoice_audit or recalculates a customer balance is part of every procedure that updates invoices. Service designs that ignore triggers lose writes the business depends on.

03

WHEN OTHERS THEN NULL

A blanket handler that swallows every exception turns a constraint violation into a silent no-op. Extraction must record which statements sit under it, because the legacy behavior on failure is 'do nothing and report success'.

04

Package state and anchored types

Package-level variables persist for the database session, so a value set by one call can change the result of the next. %TYPE and %ROWTYPE anchor variables to column definitions, which means the real precision of v_fee lives in the table DDL.

05

Dynamic SQL

EXECUTE IMMEDIATE 'UPDATE ' || v_table || ' SET ...' hides the target table in a string. Where the fragments are constant or come from a small lookup, the target set can be resolved statically; otherwise it is flagged for review with the possible values.

Extraction workflow

6 steps from source to signed-off spec.

The sequence a PYD ENTERPRISES Discovery Scan runs for this stack. Every output links back to source file and line range.

  1. 1

    Export schema and code objects

    Export DDL for tables, views, sequences and constraints plus package specs and bodies, standalone procedures, functions and triggers, for example with DBMS_METADATA or your schema repository.

  2. 2

    Parse PL/SQL and resolve anchored types

    PYD ENTERPRISES parses each unit, resolves %TYPE and %ROWTYPE against the DDL and records the precision and scale of every NUMBER involved in a calculation.

  3. 3

    Build the call graph including triggers

    Procedure calls, package references and every DML statement are linked; each DML statement is joined to the triggers it fires, so implicit writes appear on the same graph.

  4. 4

    Trace column-level lineage

    Dataflow analysis connects each written column to the columns and parameters it was computed from, through cursors, collections and BULK COLLECT.

  5. 5

    Extract decisions and side-effect inventories

    Conditional calculations become DMN 1.5 tables; each procedure gets an inventory of tables read and written, directly and through triggers, with transaction boundaries.

  6. 6

    Propose service boundaries and contracts

    Clusters of procedures that write the same tables are proposed as candidate services. Architects review them and export OpenAPI 3.1 and AsyncAPI 2.6 contracts for the boundaries they accept.

Legacy in, specification out

The source, and what PYD ENTERPRISES extracts from it.

Left: the Oracle PL/SQL construct as it sits in production. Right: the reviewed artifact your Microservices engineers build against. PYD ENTERPRISES produces the spec and the tests; your team writes the service.

LEGACY SOURCE

apply_late_fees.prcSQL
1CREATE OR REPLACE PROCEDURE apply_late_fees (p_run_date IN DATE) IS
2 CURSOR c_overdue IS
3 SELECT inv.invoice_id, inv.balance, c.segment
4 FROM invoices inv
5 JOIN customers c ON c.customer_id = inv.customer_id
6 WHERE inv.due_date < p_run_date - 15
7 AND inv.status = 'OPEN';
8 v_fee invoices.balance%TYPE;
9BEGIN
10 FOR r IN c_overdue LOOP
11 IF r.segment = 'GOV' THEN
12 v_fee := 0;
13 ELSE
14 v_fee := GREATEST(ROUND(r.balance * 0.015, 2), 25);
15 END IF;
16 UPDATE invoices
17 SET balance = balance + v_fee,
18 late_fee_count = late_fee_count + 1
19 WHERE invoice_id = r.invoice_id;
20 END LOOP;
21 COMMIT;
22EXCEPTION
23 WHEN OTHERS THEN NULL;
24END apply_late_fees;

EXTRACTED ARTIFACT

billing-late-fees.openapi.yamlYAML
1openapi: 3.1.0
2info: { title: Late fee assessment, version: 1.0.0 }
3paths:
4 /late-fee-runs:
5 post:
6 operationId: runLateFeeAssessment
7 x-pyd-source: apply_late_fees.prc:1-25
8 x-pyd-decision: BILL-LATEFEE-01
9 x-pyd-side-effects:
10 - UPDATE invoices.balance, invoices.late_fee_count
11 - TRIGGER trg_invoices_au INSERT invoice_audit
12 requestBody:
13 content:
14 application/json:
15 schema:
16 type: object
17 required: [runDate]
18 properties:
19 runDate: { type: string, format: date }
20 responses:
21 "202": { description: Run accepted }
22# BILL-LATEFEE-01 (hit policy FIRST)
23# segment = "GOV" -> fee 0
24# daysPastDue > 15, other -> max(round(balance * 0.015, 2), 25)
25# review: WHEN OTHERS THEN NULL swallows all errors

Architectural best practices

Build the Microservices side to the spec, not to the old shape.

  • Draw service boundaries around the tables a cluster of procedures writes, including writes made by triggers, not around package names.
  • Replace swallowed exceptions with explicit, reviewed failure behavior and make partial-run semantics a documented decision.
  • Keep set-based SQL where it is the right tool; move the decision logic, not every statement, into application code.
  • Publish each extracted side-effect inventory with the service contract so downstream teams know which events replace which trigger writes.
  • Use the generated fixtures against both the stored procedure and the new service during parallel run until results match.

The database is not a dumb store here

In many Oracle shops, the application tier is thin and the packages are the system. Moving to microservices means each service needs to own its data, but the rules that keep that data consistent are spread across procedures, triggers, check constraints and scheduled jobs. Split the schema before you know where those rules are and you will reimplement half of them and lose the rest.

PYD ENTERPRISES builds the inventory first: every rule, every write path and every trigger, linked to the PL/SQL lines that implement it. It does not generate the services. Your architects use the inventory to decide boundaries, and your engineers build against the reviewed decision tables and contracts.

Reading the snippet above as a spec

Four facts come out of apply_late_fees that a reviewer must confirm:

  • Government customers never pay a late fee, but their late_fee_count still increments on every run.
  • The fee is 1.5% of the balance rounded to cents, with a floor of 25.00, applied on every run to open invoices more than 15 days past due.
  • A failure partway through is swallowed: earlier updates stay pending in the transaction, the procedure returns normally, and whether they persist depends on the caller's next COMMIT or ROLLBACK.
  • Every update also fires the audit trigger, so the target design needs an audit event for each fee.

The first and third facts are the ones that usually surprise the business, and they are why extraction comes before design.

FAQ

Oracle PL/SQL to Microservices questions

Does PYD ENTERPRISES need access to a live Oracle database?

No. Extraction runs on exported DDL and PL/SQL source. Access to a non-production database is useful later to run generated fixtures against the existing procedures during parallel run.

How are triggers shown in the output?

Each DML statement in a procedure is linked to the triggers it fires, and the trigger's own reads and writes are added to the procedure's side-effect inventory with their source lines, so implicit behavior is visible in the same view.

Can PYD ENTERPRISES resolve EXECUTE IMMEDIATE?

When the SQL string is built from constants, bind variables or a small resolvable lookup, yes. When it depends on runtime data the analyzer cannot bound, the statement is flagged for review with the fragments it could determine.

Is T-SQL supported the same way?

Yes. See the T-SQL stored procedures to .NET services guide for SQL Server specifics such as temp tables, @@ROWCOUNT checks and TRY/CATCH.

Get the spec for your Oracle PL/SQL estate.

Request a codebase scan and review extracted rules, contracts and fixtures for your own Oracle PL/SQL estate, inside your VPC.