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 IDEAt the time of this writing, we only have instructions for Cursive. |
Cursive users: Activate the |
user-remote
Create 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 IDEAt 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-service
Set 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-service
Create 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 info
By 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 test
Let’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 is a website building & hosting documentation for Clojure/Script libraries
× close