Tutorial¶
Info
If in order to study things you prefer to analyse code by yourself, rather than go through detailed step-by-step tutorials like this one, then maybe Example Djig Application will help you better.
In this tutorial we are going to create an application with dynamic beans managed by djig.
The app will consist of three projects:
- api
- app
- dynamic
It will be a web server with just one endpoint GET /hello
returning a greeting text.
The greeting text will be formed by a dynamic bean, sitting in the dynamic
project.
We'll be able to change the greeting without restarting the app.
When we launch the application for the first time, GET /hello
will return Hello, world!
.
After that we will change the code creating the message in the dynamic
project,
and then, without restarting the app, GET /hello
will start to return Hi, world!
.
In this tutorial, we'll be using Java, Gradle and GitLab.
Note
Djig can also be used with Maven, but for this tutorial we recommend Gradle.
For Gradle djig projects there is the org.taruts.workspace Gradle plugin, which simplifies working with multiple Git repository code bases. Also, there is the org.taruts.djig Gradle plugin, simplifying creation of a developer's personal local copies of dynamic projects.
In this tutorial we will cover working with those plugins.
Arranging Local Project Directories¶
First, create a directory djig-test
at whatever place in your file system.
This will be the parent directory for the directories of our three projects.
1 |
|
The api project¶
This project will build the API JAR and publish it to the local Maven repository.
The app
and dynamic
projects will depend on the API JAR.
Creating the api Directory¶
1 2 |
|
Configuring the Build¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
- Read about
djig-dynamic-api
in more detail here
.gitignore¶
1 2 3 4 5 6 7 8 9 |
|
- Files, that the IDE creates to work with the project, that are not normally pushed
1 2 3 4 5 6 7 8 |
|
- Files, that the IDE creates to work with the project, that are not normally pushed
The MessageProvider interface¶
Let's add a dynamic bean interface MessageProvider
to the api
project, with which we're going to build our API JAR.
The API JAR will be in the dependencies of both the app
and dynamic
projects.
dynamic
will use the MessageProvider
interface to define a dynamic bean, implementing this interface.
app
will use the MessageProvider
interface,
in its beans (in its single controller),
to define a dependency on the dynamic bean, defined in dynamic
.
api/src/main/java/x/dynamic/api/MessageProvider.java | |
---|---|
1 2 3 4 5 6 7 |
|
- Note that
MessageProvider
being a dynamic bean interface must inherit the DynamicComponent marker interface
Publishing the API JAR¶
While in the api
directory let's run the following command:
1 |
|
1 |
|
The dynamic project¶
Creating the dynamic Directory¶
1 2 |
|
Configuring the Build¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
- A dependency on the API JAR, that we've defined in the
api
project and published to the local Maven repository
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
- A dependency on the API JAR, that we've defined in the
api
project and published to the local Maven repository
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
- A dependency on the API JAR, that we've defined in the
api
project and published to the local Maven repository
.gitignore¶
1 2 3 4 5 6 7 8 9 |
|
- Files, that the IDE creates to work with the project, that are not normally pushed
1 2 3 4 5 6 7 8 |
|
- Files, that the IDE creates to work with the project, that are not normally pushed
The MessageProviderImpl dynamic bean¶
Let's go to the dynamic
project and create a dynamic bean there,
implementing the MessageProvider
dynamic interface,
which is from the api
project and, consequently, from the API JAR.
dynamic/src/main/java/x/dynamic/MessageProviderImpl.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Publishing the dynamic project to GitLab¶
Go to GitLab and create an EMPTY, PRIVATE repository dynamic
.
Be sure not to select the checkbox adding a README.MD
to the project, we need an absolutely empty repository.
Copy the HTTPS clone URL of the newly created repository to the clipboard.
Now go to the project local directory and run the following commands to push the dynamic
project to GitLab.
1 2 3 4 5 |
|
The app Project¶
Creating the app Directory¶
1 2 |
|
Creating the Working Directory¶
At runtime the app will clone dynamic projects into its working directory.
So, in order not to create problems in working with Git,
the working directory should not be the root of the app
directory itself.
As the working directory we will use the working-directory
subdirectory of the app
directory.
1 2 |
|
Later we will add working-directory
to the .gitignore
.
Configuring the Build¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
- Instead of
spring-boot-starter-webflux
you can as well usespring-boot-starter-web
- A starter adding djig to a project
- A dependency on the API JAR, that we've earlier defined in the
api
project and published to the local Maven repository
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
- Instead of
spring-boot-starter-webflux
you can as well usespring-boot-starter-web
- A starter adding djig to a project
- A dependency on the API JAR, that we've earlier defined in the
api
project and published to the local Maven repository
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
- Instead of
spring-boot-starter-webflux
you can as well usespring-boot-starter-web
- A starter adding djig to a project
- A dependency on the API JAR, that we've earlier defined in the
api
project and published to the local Maven repository
.gitignore¶
1 2 3 4 5 6 7 8 9 10 11 |
|
- Files, that the IDE creates to work with the project, that are not normally pushed
- The directory that all dynamic projects will be cloned to at runtime
1 2 3 4 5 6 7 8 9 10 |
|
- Files, that the IDE creates to work with the project, that are not normally pushed
- The directory that all dynamic projects will be cloned to at runtime
application.properties¶
Now let's reference the dynamic
project, we've just published to GitLab,
in the Spring Boot configuration properties of the app
project.
Thus app
will know, that the dynamic project dynamic
exists and where it is.
src/main/resources/application.properties | |
---|---|
1 2 3 |
|
Webhook Configuration¶
As any Spring Boot application by default, ours will listen on port 8080
of all network interfaces of the computer.
It might be that none of them is accessible from the public internet. If so then the app won't be able to receive callback requests from GitLab directly.
If this is you case - don't worry, you'll be able to see the app in action, simulating receiving callback requests by manually opening the corresponding URL in the browser.
But, if there is a network interface with a public IP on your computer, or there is a port forwarding from NAT configured on your router, or there is a reversed proxy accessible form outside, forwarding HTTP requests to your interface, then we will see the fully automated dynamic code redeployments.
If so - use the properties djig.hook.host
, djig.hook.port
and djig.hook.protocol
in src/main/resources/application.properties
to specify the corresponding parts of the URL that webhook requests should target.
Info
If your interface is acessible from outside indirectly, as we've described above, then those properties must point to the NAT or reverse proxy, not to the interface of the app.
More about indirect webhook callback requests see here.
config / application.properties¶
We will keep our GitLab credentials in a separate file.
This is a good practice in respect to security,
as this file can be specified in the .gitignore
to prevent sending it outside via git push
.
1 2 |
|
Also, instead of username and password you can specify your Personal Access Token (PAT):
1 |
|
The AppApplication Spring Boot Application Class¶
Let's add to app
a standard Spring Boot application class.
app/src/main/java/x/app/AppApplication.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
The HelloController Controller¶
Let's add to app
a controller, serving the GET /hello
endpoint.
app/src/main/java/x/app/HelloController.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Running the app¶
Warning
Make sure you run the app with working-directory
as its working directory!
- Run the
app
- Open http://localhost:8080/hello, you should receive
Hello, world!
- Let the tab open, we'll need it later
Changing the Dynamic Code at runtime¶
Go to the dynamic
project, to the x.dynamic.MessageProviderImpl
class and replace the Hello, world!
with Hi, world!
:
dynamic/src/main/java/x/dynamic/MessageProviderImpl.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Now push the changes.
If we've correctly configured the djig.hook.*
properties,
then the app
will receive a callback request from GitLab
at URL http://localhost:8080/refresh/generic/1.
If we didn't configure the djig.hook.*
properties,
then we can imitate the callback request by manually opening this URL in the browser:
http://localhost:8080/refresh/generic/1.
Regardless the way the callback request has come, dynamic code redeployment process will start.
After some seconds refresh the tab with http://localhost:8080/hello.
You will see Hi, world!
.
Success
This tells us we've successfuly change the dynamic code without stopping the app!
It was Hello, world!
, and now it's Hi, world!
.
Using Static Beans In Dynamic Beans¶
Let's now change the architecture a bit.
Let's now have the punctuation mark at the end of the greeting be formed by the main part of the application.
The dynamic bean will still be responsible for building the greeting, but it will delegate the choice of the punctuation mark to a bean in the static part of the application.
Here's the plan:
- Go to
api
and add an interface for a new bean inapp
that will provide the punctuation mark - In
dynamic
change the dynamic beanMessageProviderImpl
so that it would delegate the decision about the punctuation mark to the new bean inapp
- Add a bean implementing the new interface to
app
Adding the New Interface to api
¶
Let's add a new interface x.dynamic.api.PunctuationMarkProvider
to api
:
api/src/main/java/x/dynamic/api/PunctuationMarkProvider.java | |
---|---|
1 2 3 4 5 |
|
Note that this interface does NOT extend org.taruts.djig.dynamicApi.DynamicComponent, because it is NOT a dynamic bean interface, it is for a STATIC bean.
Now open the terminal, go to the api
directory and run gradle publish
.
The new API JAR will be published to the local Maven repository.
Changing the Dynamic Bean in dynamic
¶
Go to the dynamic
project.
Info
We've just published the API JAR for the second time
with the same artifact version.
If we use an IDE, we might need
to synchronize the IDE dependencies with those of the Gradle build.
E.g. in Intellij IDEA 2022.3.2 this is done with
Gradle tool window
/ right mouse button on the dynamic
tree node / context menu element Reload Gradle project
or Refresh Gradle dependencies
.
You might need to run both, one after another.
Let's change x.dynamic.MessageProviderImpl
dynamic/src/main/java/x/dynamic/MessageProviderImpl.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
- Adding a dependency on the static bean via the
PunctuationMarkProvider
interface from the API JAR - Delegate the decision about the punctuation mark at the end of the greeting to the static bean
Commit and push the changes to GitLab.
Add the implementation to app
¶
Go back to app
.
Stop the application if it's still running.
Again, if we're using an IDE, synchronize the dependencies with the Gradle build.
In app
create a new class x.app.PunctuationMarkProviderImpl
implementing the previously created interface in API JAR:
app/src/main/java/x/app/PunctuationMarkProviderImpl.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Testing the Changes¶
Now rebuild the app and start it all over again.
Refresh the tab with http://localhost:8080/hello,
we should now see
Hi, world!!!!!!!!!!
.
Success
The exclamation marks are in place, we can see that the dynamic bean uses the dependency on the bean from the main part of the app!
Adding a Dynamic REST Endpoint¶
Note
You can read more about dynamic endpoints in Dynamic Endpoints.
Let's add a new REST endpoint to our application, without stopping it.
Start the application if it isn't running.
Adding a Controller to dynamic
¶
Go to dynamic
and add the following controller:
dynamic/src/main/java/x/dynamic/DynamicController.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
- Because this is a controller from a dynamic project, the app will add the
/dynamic-project/<project name>/
prefix to the pathsome/text
. In our case, as the project name is1
, the prefix will be/dynamic-project/1/
, and the full URL will be http://localhost:8080/dynamic-project/1/some/path.
Push the changes.
After some seconds open this in the browser: http://localhost:8080/dynamic-project/1/some/path.
You'll get some text
in response.
Success
We've successfully added the REST endpoint http://localhost:8080/dynamic-project/1/some/path to our application without stopping the app!
Using the workspace Gradle Plugin to Automate Work with Multiple Git Repositories¶
At the moment our code base has three repositories:
api
dynamic
app
If we had one more dynamic project, we'd have 4 or 5.
4 - if the second dynamic project used the API JAR from our api
project,
5 - if the second dynamic project used a separate API JAR with its sources in a separate Git repository.
In other words, if you work with djig you may have to deal with many Git repositories.
To simplify dealing with many Git repositories in djig we recommend using the org.taruts.workspace Gradle plugin.
This allows to rid new developers, starting to work on the product, of the necessity to clone a lot of Repositories manually.
Instead, they would need to do the following:
- Clone just one repository (
workspace
) - Set up their GitLab or GitHub credentials
- Run
gradle cloneAll
Of course, if there are just three repositories, you can't say for sure this simplifies much, but if the number is five or more, the benefits of using the workspace Gradle plugin get more noticeable.
So, let's add the workspace Gradle plugin to our codebase.
Putting the app and api Projects on GitLab¶
So far only the dynamic
project was on GitLab.
Now we're going to add app
and api
as well.
Go to GitLab and add EMPTY, PRIVATE projects api
and app
there.
Be sure they are ABSOLUTELY EMPTY, without README.MD
or something else.
Now we're going to init local Git repositories in the api
and app
directories,
and commit and push the projects to GitLab.
For the api Project¶
1 2 3 4 5 6 |
|
For the app Project¶
1 2 3 4 5 6 |
|
Creating the workspace repository¶
The workspace
repository, as we wrote above, is the only repository,
that a new developer will clone manually.
Other repositories (api
, dynamic
and app
) will be cloned automatically.
Creating the workspace Local Directory¶
1 2 |
|
Configuring the Build¶
1 2 3 |
|
1 2 3 |
|
.gitignore¶
1 2 3 4 5 6 7 8 9 10 11 12 |
|
- The parent directory, in which
gradle cloneAll
will clone repositories - Files, that the IDE creates to work with the project, that are not normally pushed
Setting up GitLab credentials¶
There are a lot of ways to specify Git repository hosting credentials for workspace.
We'll be using only one of them, which is having the credentials in ~/.gradle/gradle.properties
:
~/.gradle/gradle.properties | |
---|---|
1 2 |
|
Or, if you prefer to use Personal Access Token (PAT) then specify it as username
:
~/.gradle/gradle.properties | |
---|---|
1 |
|
Adding the workspace Project on GitLab¶
Log in at GitLab and a new EMPTY, PRIVATE repository workspace
in the same project group with api
, dynamic
and app
.
Be sure not to add README.MD
, the repository must be COMPLETELY EMPTY.
Copy the HTTPS clone URL.
Pushing workspace to GitLab¶
1 2 3 4 5 6 |
|
Automatic Cloning of all other Projects¶
Run the following:
1 2 |
|
In the projects
directory there appeared api
, app
and dynamic
.
Success
We have successfully applied the workspace Gradle plugin to enable automatic cloning of a group of repositories.
Warning
The list of repositories to clone is built after adding the workspace Gradle plugin to the build (actually, when any task is launched afterwards).
If, after the list of repositories is created,
you add another repository to the GitLab project group where the workspace
project sits,
the list will stay the same, and cloneAll
won't clone the new repository.
For example, if by mistake you run cloneAll
before all the repositories are on GitLab,
and you add them afterwards, consequent cloneAll
launches won't clone them.
To make cloneAll
see the new repositories, run gradle refreshProjectsList
.
After that, in a separate launch of gradle
execute gradle cloneAll
(running them together in gradle refreshProjectsList cloneAll
won't have the desired effect).