In production, your deployable projects, by their nature, run in separate processes.
In development, you are running from a single REPL process, which can lead to classpath issues you will never encounter in production.
Profiles allow you to work on all your projects from the development project without classpath issues.
| You likely don’t need profiles if your workspace does not re-specify one interface. |
In our ongoing tutorial, we last left off exploring testing.
If you’ve been following along, your development project currently mirrors the command-line project:
The cli base references the user component in both projects.
Let’s pretend that we have discovered performance problems in the user component and have learned that distributing the load by delegating to a new service should solve the problem:
The cli base now references a new user-remote component, which calls a new user-api base in a new user-service project, which references our original user component.
The production environment looks good, but how about the development environment?
We have a problem.
The user and user-remote components both specify the user interface, resulting in two se.example.user.interface namespaces on the classpath.
Duplicate items on the classpath will confuse classloaders and IDEs.
|
Classloader issues are only a
|
The poly solution is to use profiles:
We can add default and remote profiles to avoid duplication on the classpath, allowing us to develop all our code and projects from a single place without issues.
If no other profiles are selected, poly automatically merges the default profile into the development project.
Let’s move from our current design:
…to our new one:
First, we need some mechanism for the command-line tool to communicate over the wire with the user-service.
After some searching, we found the slacker library.
It supports simple remote procedure calls.
Perfect for our tutorial.
Here’s a checklist to implement our design:
1. Create the user-api base
2. Create the user-remote component
3. Switch from user to user-remote in the command-line project
4. Create the user-service project
5. Build the user-service
user-api basea. Create the user-api base
b. Add the slacker library to the user-api base
c. Add user-api base to ./deps.edn:
d. Implement the server for the user-api base
slacker library to the user-api baseEdit user-api base deps.edn:
{:paths ["src" "resources"]
:deps {slacker/slacker {:mvn/version "0.17.0"}} (1)
:aliases {:test {:extra-paths ["test"]
:extra-deps {}}}}
| 1 | Add slacker library dependency for server-side communication support |
user-api base to ./deps.edn:Adjust ./deps.edn like so:
:aliases {:dev {:extra-deps [...
poly/user-api {:local/root "bases/user-api"} (1)
...]
:test {:extra-paths [...
"bases/user-api/test" (2)
| 1 | Add user-api source dep under :dev alias |
| 2 | Add user-api test path under :test alias |
user-api baseCreate the api namespace in the user-api base:
example
├── bases
│ └── user-api
│ └── src
│ ├── se.example.user_api.api.clj (1)
│ └── se.example.user_api.core.clj
| 1 | Create the new api.clj file |
Set the content of api.clj to:
(ns se.example.user-api.api
(:require [se.example.user.interface :as user]))
(defn hello-remote [name]
(user/hello (str name " - from the server")))
Update core.clj to:
(ns se.example.user-api.core
(:require [se.example.user-api.api]
[slacker.server :as server])
(:gen-class))
(defn -main [& args]
(server/start-slacker-server [(the-ns 'se.example.user-api.api)] 2104)
(println "server started: http://127.0.0.1:2104"))
user-remote componenta. Create the user-remote component
b. Add the slacker library to user-remote component
c. Remove the user component from ./deps.edn:
d. Add the default and remote profiles to ./deps.edn:
e. Activate the remote profile in your IDE
f. Implement user-remote
g. Activate the default profile in your IDE
slacker library to user-remote componentEdit user-remote component deps.edn:
{:paths ["src" "resources"]
:deps {slacker/slacker {:mvn/version "0.17.0"}} (1)
:aliases {:test {:extra-paths ["test"]
:extra-deps {}}}}
| 1 | Add slacker lib dependency for client-side communication support |
user component from ./deps.edn:{:aliases {:dev {...
:extra-deps {poly/user {:local/root "components/user"} (1)
poly/cli {:local/root "bases/cli"}
poly/user-api {:local/root "bases/user-api"}
org.clojure/clojure {:mvn/version "1.12.0"}}}
:test {:extra-paths ["components/user/test" (2)
"bases/cli/test"
"projects/command-line/test"
"bases/user-api/test"]}
| 1 | Delete poly/user {:local/root "components/user"} |
| 2 | Delete "components/user/test" |
default and remote profiles to ./deps.edn::aliases {...
:+default {:extra-deps {poly/user {:local/root "components/user"}} (1)
:extra-paths ["components/user/test"]}
:+remote {:extra-deps {poly/user-remote {:local/root "components/user-remote"}} (2)
:extra-paths ["components/user-remote/test"]}
| 1 | Respecify your deleted user component under the default profile alias |
| 2 | Specify your new user-remote component under the remote profile alias |
Notice that profile aliases are prefixed with a +.
remote profile in your IDE| At the time of this writing, we only have instructions for Cursive. |
|
Cursive users: Activate the
|
user-remoteCreate the core namespace in the user-remote component:
example
├── components
│ └── user-remote
│ └── src
│ ├── se.example.user.core.clj (1)
│ └── se.example.user.interface.clj
| 1 | Create new core.clj file |
Set core.clj content to:
(ns se.example.user.core
(:require [slacker.client :as client]))
(declare hello-remote)
(defn hello [name]
(let [connection (client/slackerc "localhost:2104")
_ (client/defn-remote connection se.example.user-api.api/hello-remote)]
(hello-remote name)))
And update the interface.clj content to:
(ns se.example.user.interface
(:require [se.example.user.core :as core]))
(defn hello [name]
(core/hello name))
default profile in your IDE| At the time of this writing, we only have instructions for Cursive users. |
|
Cursive users: Edit the REPL configuration:
…and add the We now need to include the We have segregated the two components that specify a For the changes to take effect, you need to restart the REPL. Normally, a REPL restart is not required, but when adding profiles, it’s necessary. |
user to user-remote in the command-line projectuser with user-remote in command-line projectMake the following changes to the command-line project deps.edn:
{:deps {poly/user {:local/root "../../components/user-remote"} (1)
poly/cli {:local/root "../../bases/cli"}
org.clojure/clojure {:mvn/version "1.12.0"}
org.slf4j/slf4j-nop {:mvn/version "2.0.9"}} (2)
:aliases {:test {:extra-paths ["test"]
:extra-deps {}}
:uberjar {:main se.example.cli.core}}}
| 1 | Rename components/user to components/user-remote.
It’s okay to leave poly/user as is; it’s unique within the project. |
| 2 | Add logging library (slacker lib does some logging that we’ll ignore) |
user-service projecta. Create the user-service project:
b. Configure the user-service
c. Add a poly alias for the user-service
user-serviceSet the user-service project deps.edn content to:
{:deps {poly/user {:local/root "../../components/user"} (1)
poly/user-api {:local/root "../../bases/user-api"} (2)
org.clojure/clojure {:mvn/version "1.12.0"}
org.slf4j/slf4j-nop {:mvn/version "2.0.9"}} (3)
:aliases {:test {:extra-paths []
:extra-deps {}}
:uberjar {:main se.example.user-api.core}}} (4)
| 1 | Add user component |
| 2 | Add user-api base |
| 3 | Add logging library (slacker lib does some logging that we’ll ignore) |
| 4 | Specify main for uberjar artifact |
poly alias for the user-service :projects {"development" {:alias "dev"}
"command-line" {:alias "cl"}
"user-service" {:alias "user-s"}}} (1)
| 1 | Add user-s alias for your new user-service |
user-serviceCreate an uberjar for the user-service:
clojure -A:deps -T:build uberjar :project user-service
Phew, that should be it! Now, let’s test if it works.
From a separate terminal, launch the user-service:
cd projects/user-service/target
java -jar user-service.jar
You should see the following output:
server started: http://127.0.0.1:2104
|
Cursive users:
Now that you have a running service, you can test if you can call it from the REPL.
You activated the remote profile in your IDE earlier, which made the Note that this only instructs the IDE to treat
…but it doesn’t automatically load its source code into the REPL! You can verify this by adding this code to
…and if you execute the
Remember, you set your REPL configuration to include the Let’s create a REPL configuration that includes the remote profile:
This REPL will use the But let’s continue with the REPL that is already running and see if we can switch to Open the If you execute the
|
Now, let’s continue with our example.
From another terminal (not the one from which you started the user-service) from your example workspace root dir:
cd projects/command-line/target
java -jar command-line.jar Lisa
You should see:
Hello Lisa - from the server!!
If your output matches, congratulations, you’ve successfully exercised poly profiles!
| You can find the complete tutorial code here. |
Now execute the info command (+ deactivates all profiles, and makes the default profile visible):
cd ../../.. (1)
poly info +
| 1 | Navigate back to the workspace root dir |
…and compare the info output with our target design:
Great! Reality now matches our plan!
Notice that profile flags only include the st flags and never the x flag.
Whether or not to run tests is not tied to profiles.
|
This example was quite simple, but if your project is more complicated, you may want to manage state during development with a tool like Mount, Component, or Integrant.
You could also create your own helper functions in your development project namespace ( |
poly infoBy default, the default profile is active:
poly info
Notice:
default is listed for active profiles
the dev project column:
includes the user brick (which is in the default profile)
doesn’t include the user-remote brick (which is in the remote profile)
columns for the inactive remote profile are shown
Profiles can also contain dependencies and paths to projects, but we’ve done no such thing in our example; therefore, you’ll see all profile flags as -- in the project section.
|
You can override the default profile by specifying a profile:
poly info +remote
Notice:
remote is listed for active profiles
that the dev project column:
doesn’t include the user brick (which is in the default profile)
includes the user-remote brick (which is in the remote profile)
columns for the inactive default profile are shown
You can specify more than one profile:
poly info +default +remote
Notice:
default and remote are listed as active profiles
that the dev project column:
includes the user brick (which is in the default profile)
includes the user-remote brick (which is in the remote profile)
no inactive profile columns are shown
poly tells us that it does not like that we included both user and user-remote in the development project
Let’s see how many lines of code we have by specifying the :loc argument:
poly info :loc
Under bricks, each project column tallies the lines of code for its bricks src code.
The loc column counts the number of lines of codes for src directories, while (t) counts for the test directories.
poly testLet’s run all the tests to verify that everything works:
poly test :project
If your output matches, all that green is a very good sign; pat yourself on the back!
Can you improve this documentation? These fine people already did:
Joakim Tengstrand & Sean CorfieldEdit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |