While developing an APEX app, I check my latest changes into a Git repository after reaching a feature milestone. In this article I’ll explain how I took advantage of APEX’s scriptable devops features to automate multiple steps in my daily workflow that I had previously been doing manually. The result are two simplified commands:
apex2git
– exports an APEX app to a Git work area, staging changed filesgit2apex
– imports an app from a Git work area to an APEX workspace
In case the scripts could be useful to you to study or use, I’ve included a link to download the source at the end of the article along with a short video that illustrates how I use these commands in my daily work.
My Workflow Before Optimization
When adding a new feature to an existing app, I used to do all of the following steps one by one:
- pull the latest app version from remote Git repo to my local work area
- create a Git branch named after the feature ticket I’m working on
- import the app from my Git work area into my APEX dev workspace
- implement the new feature using the APEX App Builder
- export the enhanced app to a temporary staging directory (e.g.
/tmp/f1234_stage
):- as a single SQL file (e.g.
f1234.sql
), and also - as individual component SQL files organized into directories, and also
- as readable YAML for easy diffing
- as a single SQL file (e.g.
- copy new
f1234.sql
file to Git work area asmy-app-name.sql
- review the diffs between temp dir files and those in the Git work area
- cherry-pick changed files from the temp dir and copy to Git work area
- add the changed files to the Git staging area
- commit the staged changes to the local Git repo
- push the changes up to the remote team Git repo
- initiate a merge request for my branch’s changes
That’s a lot of steps. Luckily I was able to simplify some of them as I learned a few new tricks.
Git Automatically Ignores Unchanged Files
The first optimization opportunity I discovered was a happy accident. Prior to this revelation, I was using a tool — Beyond Compare on Windows or Kaleidoscope on Mac — to compare the APEX export files in the temporary directory with corresponding files in the Git work area to identify which ones my feature work had changed. Then I would carefully copy only the changed files over to the Git work area.
Once, by mistake, I copied all of the files from the temp directory to the work area, and even mistakenly added them all to the Git staging area. After doing this unintentional blunder, I typed git status
. To my surprise, despite all the files having a later timestamp, Git had automatically ignored adding to the staging area any file whose logical contents had not actually changed! This productivity gift meant I could simply bulk copy all the exported files to my work area without manually cherry-picking the changed files myself. Bravo, Git!
Using SQLcl to Export APEX Apps
While exporting and importing APEX apps in the browser is straightforward, I researched how I could do it from the command line instead. This felt like an enabling ingredient to automating more of my common workflow. I learned that SQLcl supports the apex export
command that looked promising.
Through a series of trials, I converged on the following command that, given an application’s numeric id (e.g. 1234
), would export it as a single SQL file into a target directory (e.g. /tmp/f1234_stage
), including supporting objects, using original component ids, and skipping the inclusion of the export date. While the command is intended to be entered as a single line, I’ve formatted it below for better readability:
$ sql appuser/password@host:port/servicename
SQL> apex export -applicationid 1234
-dir /tmp/f1234_stage
-skipExportDate
-expOriginalIds
-expSupportingObjects Y
-expType APPLICATION_SOURCE
The result of running the apex export
command above is the single file f1234.sql
containing the entire application source for application 1234, and this file gets created in the /tmp/f1234_stage
directory.
The option to include supporting objects is self-documenting, but it’s important to understand the other two. Excluding the export date ensures component files with no other changes don’t differ from the existing version only due to the date on which they were exported. Using original ids maintains internal id numbers of components as teammates import the app and export it again from other APEX workspaces. Failure to use either of these two latter options could lead to unnecessary differences in files. Of course it’s also important to follow the best practice of using the same workspace id and be running the same APEX release (including patch set!) across different environments like Dev, Test, Prod, and private development. Failure to follow this best practice can cause uninteresting differences in the component files due only to the different workspace id values or different patchset levels.
While the single SQL file is useful for one-click installation, I also want to retrieve the individual application component SQL files organized into a directory hierarchy. This makes it easier to understand what components have been added, changed, or removed with each new feature. This requires adding the -split
command line option. So the command to do an application export split into separate component SQL files is:
SQL> apex export -applicationid 1234
-dir /tmp/f1234_stage
-skipExportDate
-expOriginalIds
-expSupportingObjects Y
-expType APPLICATION_SOURCE
-split
This results in a directory named f1234
containing all the split SQL component files organized into subdirectories, and the f1234
directory gets created inside the /tmp/f1234_stage
directory.
Starting with APEX 22.1, the readable YAML export option allows much easier code reviews since it presents the application component metadata in an easy-to-understand text outline with property names and values that match what you see as a developer in the APEX builder. To export the readable YAML version of an app, we can use the new READABLE_YAML
export type:
apex export -applicationid 1234
-dir /tmp/f1234_stage
-skipExportDate
-expOriginalIds
-expSupportingObjects Y
-expType READABLE_YAML
This results in a directory named readable
containing readable YAML files for application artifacts organized into subdirectories, and the readable
directory gets created inside the /tmp/f1234_stage
directory.
Using SQLcl 22.1 against an APEX 22.1 workspace schema, it’s possible to include multiple export types in the same export command by using a comma-separated value for the -expType
command line option. So I was able to combine the export of the “split” application component SQL and the readable YAML into a single command like this:
apex export -applicationid 1234
-dir /tmp/f1234_stage
-skipExportDate
-expOriginalIds
-expSupportingObjects Y
-expType APPLICATION_SOURCE,READABLE_YAML
-split
This results in a directory named f1234
containing both the readable
directory of the readable YAML files as well as all the split SQL component files organized into appropriately-named subdirectories, and the f1234
directory gets created inside the /tmp/f1234_stage
directory.
Automating APEX to Git Export
The recipe I identified to export an APEX app with id 1234
into the Git work area for repository my-app
in my home directory includes these steps:
- Create a temporary staging directory
/tmp/f1234_stage
- Use SQLcl to
apex export
app1234
to this staging directory - Copy
/tmp/f1234_stage/f1234.sql
to~/my-app/my-app.sql
- Deep copy
/tmp/f1234_stage/f1234
to~/my-app
- Add all files to Git staging area (Git includes only ones that really changed)
Since I was already familiar with Apache Ant for build automation, I used it to implement the recipe above and created an apex2git
shell script and Windows batch file that simply invoke the Ant build script, passing along the command line arguments for application id, repo directory name (assumed relative to my home directory), and database connection string:
$ apex2git 1234 my-app appuser/password@host:port/servicename
Since it is common for a larger APEX application to be assembled out of several smaller applications, I implemented the Ant build script to accept the name of the Git repo directory or a subdirectory. So, for example, if you were implementing an application called MediPay that was broken down into a backoffice application and a mobile application, you might export the two distinct applications into distinct subdirectories of a medipay
Git repo directory like this:
$ apex2git 1001 medipay/backoffice $MEDIPAY_CONNECTION
$ apex2git 1002 medipay/mobile $MEDIPAY_CONNECTION
In this case involving a subdirectory, the Ant build script first converts the directory separator characters \
or /
to a hyphen to end up exporting the more meaningful application export SQL scripts into:
~/medipay/backoffice/medipay-backoffice.sql
~/medipay/mobile/medipay-mobile.sql
Using SQLcl to Import an APEX App
Importing an APEX application from the command line involves calling the apex_application_install
package to set the target workspace name, the target application id, and preference related to the installation of supporting objects, followed by running the application’s single-file SQL script.
So, for example, to import application 1001
from the medipay/backoffice
Git repo directory into workspace medipay
, we can using SQLcl’s sql
or sqlplus
to run the following PL/SQL block followed by the application’s SQL script:
begin
apex_application_install.set_workspace('medipay');
apex_application_install.set_application_id(1001);
apex_application_install.set_auto_install_sup_obj(
p_auto_install_sup_obj => true );
end;
/
@/home/smuench/medipay/backoffice/medipay-backoffice.sql
Automating Git to APEX
The recipe I identified to import an APEX app with id 1234
from Git work area my-app
in my home directory into workspace target-wksp
includes these steps:
- Create a temporary staging directory
/tmp/f1234_stage
- Copy
~/my-app/my-app.sql
to directory/tmp/f1234_stage/f1234.sql
- Use SQLcl to set install options, then run
/tmp/f1234_stage/f1234.sql
Again I employed an Ant build script to carry out these steps. Then I created a git2apex
shell script and Windows batch script that invokes the Ant build script, passing along the command line arguments for application id, repo directory name (assumed relative to my home directory), target workspace name, and database connection string:
$ git2apex 1234 my-app target-wksp appuser/password@host:port/servicename
As before, to accommodate solutions comprising multiple smaller APEX apps, I implemented the Ant build script to accept the name of the Git repo directory or a subdirectory. So, for the MediPay application comprised of a backoffice application and a mobile application, you might import the two distinct applications from distinct subdirectories of a medipay
Git repo directory into a workspace named medipay
like this:
$ git2apex 1001 medipay/backoffice medipay $MEDIPAY_CONNECTION
$ git2apex 1002 medipay/mobile medipay $MEDIPAY_CONNECTION
Ant: Using Templatized SQL Scripts
The apex2git
command runs a apex2git.xml
Ant build script that copies an apex2git.sql
template SQL script to the temporary staging directory, substituting @APPID@
and @DIR@
tokens in the process using the values of the application id and repository directory parameters passed in:
REM apex2git.sql - v1.0
apex export -applicationid @APPID@ -dir @DIR@ -skipExportDate -expOriginalIds -expSupportingObjects Y -expType APPLICATION_SOURCE
apex export -applicationid @APPID@ -dir @DIR@ -skipExportDate -expOriginalIds -expSupportingObjects Y -expType APPLICATION_SOURCE,READABLE_YAML -split
Then it runs the resulting SQL script using SQLcl’s sql
command against the provided database connection to produce the single SQL file, multiple component SQL file, and YAML exports in the temporary staging directory. It then proceeds to copy the exported files to the Git work area and add them all to the Git staging area.
Similarly, the git2apex
command runs a git2apex.xml
Ant script that substitutes command-line parameters into the following git2apex.sql
template SQL script and then uses sql
to run it against the provided database connection.
REM git2apex.sql - v1.0
REM First setup the import options
begin
apex_application_install.set_workspace('@WORKSPACE@');
apex_application_install.set_application_id(@APPID@);
apex_application_install.set_auto_install_sup_obj( p_auto_install_sup_obj => true );
end;
/
REM Then install the application by running its SQL script
@@DIR@/f@APPID@.sql
My Streamlined Workflow
Using the apex2git
and git2apex
scripts, I’ve streamlined my development workflows. When working alone on an APEX app, I tend to eschew branches and just push my changes directly to the repository, accompanied by a descriptive commit message that explains what new feature I’ve implemented:
- pull the latest app version from remote Git repo to my local work area
- import the app to my private dev workspace using
git2apex
- implement the new feature using the APEX App Builder
- export the enhanced app to Git using
apex2git
- commit the staged changes to local Git repo mentioning feature I finished
- push the changes up to the remote Git repo
In contrast, when working with others, I use feature branches and Git pull requests to facilitate code review by my peers:
- pull the latest app version from remote Git repo to my local work area
- create a Git branch named after the feature ticket I’m working on
- import the app to my private dev workspace using
git2apex
- implement the new feature using the APEX App Builder
- export the enhanced app to Git using
apex2git
- commit the staged changes to the local Git repo
- push the changes up to the remote team Git repo
- initiate a merge request for my branch’s changes for peer review
Checking Out the Scripts
In case you want to explore the scripts to see how they work, you can download them from here. Unzip the archive in your home directory, and make sure that the ./utils
subdirectory it creates is in your system path. To keep things simple, the scripts currently rely on having all the apex2git*
and git2apex*
files in this utils
subdirectory of the currently logged-in user’s home directory. Of course, you’ll also need to have SQLcl 22.1 and Apache Ant installed and have both sql
and ant
commands be available in your system path.
The simple usage documentation for the scripts is:
git2apex appId repoDirectory workspaceName connectionString
apex2git appId repoDirectory connectionString
The repository directory is interpreted relative to your home directory. You can see the commands in action in the short example video below.
Example of git2apex & apex2git in Action
In this short video, I show using git2apex
and apex2git
without using a feature branch for simplicity. This is the process I use when working alone on an APEX app that I source-control in a private Git repository up on GitHub. For example, the donor-tracking system I built for a local Italian non-profit and several personal productivity apps follow this approach. When working on sample apps as part of my day job at Oracle, we use an internal Git repository instead of GitHub for that. The git2apex
and apex2git
scripts work the same regardless of whether the git repo is only on your machine or whether it’s remote.
Next Stop: CI/CD Pipelines
APEX’s scriptable devops features let you create custom solutions to automate frequent tasks in your app development lifecycle. We’ve seen a simple example of their use in this article with apex2git
and git2apex
commands. My colleague Carsten has also documented a technique for exporting and importing APEX applications using a REST service in scenarios where that approach proves useful.
These options make it straightforward to incorporate APEX application export and import into more involved “pipelines” of continuous integration and continuous delivery. These automations are often triggered automatically by the merging of new code to the main branch of your Git repository, and can include steps that setup test data, execute unit tests and integration tests, and finally install a validated build of your application into a target environment if all tests pass. In future releases of Oracle APEX, we aim to make doing some of these steps even simpler from directly inside the builder. However, it’s good to know that in the meanwhile any kind of build automation is already possible to achieve with the ingredients APEX provides today.