<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kari Marttila</title>
    <description>The latest articles on DEV Community by Kari Marttila (@karimarttila).</description>
    <link>https://dev.to/karimarttila</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F514866%2Fea9437dd-d2f9-4fc3-a4dc-4821968323eb.jpeg</url>
      <title>DEV Community: Kari Marttila</title>
      <link>https://dev.to/karimarttila</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/karimarttila"/>
    <language>en</language>
    <item>
      <title>Good Enough Developer</title>
      <dc:creator>Kari Marttila</dc:creator>
      <pubDate>Wed, 27 Oct 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/karimarttila/good-enough-developer-3na9</link>
      <guid>https://dev.to/karimarttila/good-enough-developer-3na9</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hsN1uXd8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-10-26-real-full-stack-developer_img_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hsN1uXd8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-10-26-real-full-stack-developer_img_1.png" alt="Infra code and Clojure/script full-stack app code" width="880" height="656"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Infra code and Clojure/script full-stack app code.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;In my previous blog post &lt;a href="https://www.karimarttila.fi/cloud/2021/10/26/real-full-stack-developer.html"&gt;Real Full-Stack Developer&lt;/a&gt;, I was pondering the concept of a full-stack developer. I also wrote a blog post on the Metosin site regarding how developers co-operate together as a &lt;a href="https://www.metosin.fi/blog/superorganism/"&gt;superorganism&lt;/a&gt;. After spending time with these thoughts, I was also thinking about what makes a good developer. On the same day, I listened to an interesting Finnish podcast about &lt;a href="https://en.wikipedia.org/wiki/Impostor_syndrome"&gt;Impostor syndrome&lt;/a&gt; (&lt;a href="https://koodarikuiskaaja.fi/podcast/huijarisyndrooma-on-kettumainen-ilmio/"&gt;Huijarisyndrooma on kettumainen ilmiö&lt;/a&gt;) All these things boiled in my head, and I got an idea: I must write a short blog post with the title “Good Enough Developer.”&lt;/p&gt;

&lt;h3&gt;
  
  
  What Makes a Great Developer?
&lt;/h3&gt;

&lt;p&gt;In my personal opinion, anyone can be a great developer. You don’t have to be a virtuous programmer. What is essential is to have an open mind, a lot of curiosity, and be a relentless learner. And to have the courage to enter new areas in which you don’t have much experience - the work itself will give you experience.&lt;/p&gt;

&lt;p&gt;In addition to those qualities, if you are a superb programmer by God’s mercy, that’s a plus. I have seen a couple of really, really good programmers. It is an absolute joy to work with a virtuous programmer since you learn fast with that kind of mentorship.&lt;/p&gt;

&lt;h3&gt;
  
  
  Everything is Interpretation
&lt;/h3&gt;

&lt;p&gt;Working with a really, really good programmer can also be a bit intimidating experience. E.g., in my previous project, I worked with one of the best Clojure programmers on this planet (read more in my earlier blog post &lt;a href="https://www.karimarttila.fi/metosin/2021/09/19/my-first-project-at-metosin.html"&gt;My First Project at Metosin!&lt;/a&gt;). That’s the moment when you can choose between two different narratives. In the sad narrative, you tell yourself: &lt;em&gt;“I’ll never be as good a programmer as this guy. All hope is lost. I might as well quit and start a new career flipping burgers.”&lt;/em&gt; Or you can choose a more positive narrative and tell yourself: &lt;em&gt;“Gosh, this is just great! One of the best clojurians on the planet is reviewing my every PR - what a great learning opportunity this is!”&lt;/em&gt; Remember: Everything is interpretation - choose a narrative that is easier to live with.&lt;/p&gt;

&lt;h3&gt;
  
  
  Impostor Syndrome
&lt;/h3&gt;

&lt;p&gt;When working in a company full of great programmers, you might feel that you are not as good as the other programmers, and they soon see through you. You will be exposed. An average programmer. That’s the &lt;a href="https://en.wikipedia.org/wiki/Impostor_syndrome"&gt;Impostor syndrome&lt;/a&gt; talking, don’t give it any power over you. Once you choose that negative narrative, you will teach your brain that you lack something and are not worth enough. That’s bullshit. If you are working as a programmer, most probably someone already is happy with your work. Relax. Create a more positive narrative for yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Good Enough
&lt;/h3&gt;

&lt;p&gt;You can tell yourself: &lt;em&gt;“It’s pretty darn sure I’m not the most talented programmer in this company. But that’s ok. I’m good enough. With the help of these great programmers, I can be pretty productive, bring food to the table and enjoy my work. And I can learn every day! And if I really want to grow as a programmer, isn’t it good to be among better programmers you can learn from than with some programmers who have nothing to teach you?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Being good enough is a comforting feeling. In most jobs, you don’t have to be the best of the best of the best - usually good enough, is good enough. I think I’m a pretty good example of this. I don’t consider myself a great clojurian. But with my experiences creating infra code &lt;em&gt;and&lt;/em&gt; also be able to implement full-stack apps using Clojure/script (with occasional help from my colleagues in our #tech-clojure Slack channel) - I’m pretty productive member of our company. I enjoy my work enormously, and I can also give back to our community with my IaC knowledge. Now that I think about it, I kind of like being a generalist. I’m not a superhero of any specialty, but I’m good enough in IaC and various programming languages so that I can be a Good Enough &lt;a href="https://www.karimarttila.fi/cloud/2021/10/26/real-full-stack-developer.html"&gt;Real Full-Stack Developer&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;Be merciful to yourself. Don’t create negative narratives about your competence and your career. Be positive. Most often good enough, is good enough. And you can learn every day to be a better programmer.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The writer is working at Metosin using Clojure in cloud projects. If you are interested to start a cloud or Clojure project in Finland or you are interested in getting cloud or Clojure training in Finland you can contact me by sending an email to my Metosin email address or contact me via LinkedIn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Kari Marttila&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kari Marttila’s Home Page in LinkedIn: &lt;a href="https://www.linkedin.com/in/karimarttila/"&gt;https://www.linkedin.com/in/karimarttila/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Real Full-Stack Developer</title>
      <dc:creator>Kari Marttila</dc:creator>
      <pubDate>Tue, 26 Oct 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/karimarttila/real-full-stack-developer-40af</link>
      <guid>https://dev.to/karimarttila/real-full-stack-developer-40af</guid>
      <description>&lt;p&gt;(First published in &lt;a href="http://www.karimarttila.fi/cloud/2021/10/26/real-full-stack-developer.html"&gt;karimarttila.fi&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hsN1uXd8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-10-26-real-full-stack-developer_img_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hsN1uXd8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-10-26-real-full-stack-developer_img_1.png" alt="Infra code and Clojure/script full-stack app code" width="880" height="656"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Infra code and Clojure/script full-stack app code.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;In my previous blog posts (&lt;a href="https://www.karimarttila.fi/iot/2021/10/11/aws-iot-first-reflections.html"&gt;AWS IoT First Reflections&lt;/a&gt; and &lt;a href="https://www.karimarttila.fi/iot/2021/10/17/aws-iot-storage.html"&gt;AWS IoT Storage Considerations&lt;/a&gt;), I described my first AWS IoT platform project and its infrastructure building process. Now I have moved on to implementing the full-stack Clojure/script app to visualize the IoT data on that platform. While developing the full-stack app, I realized that I need to think about what full-stack app development really is and who is a full-stack developer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traditional Definition
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.w3schools.com/whatis/whatis_fullstack.asp"&gt;w3schools&lt;/a&gt; provides a concise definition for a full-stack developer: &lt;em&gt;A full stack web developer is a person who can develop both client and server software.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I have been in IT business for some 25 years. I remember the Java application server era when we created HTML UIs using JSP, Java Server Faces, and other mechanisms that were popular at that time. Later on, the so-called &lt;a href="https://en.wikipedia.org/wiki/Single-page_application"&gt;single-page application&lt;/a&gt; paradigm got popular, and developers started using Javascript to implement the HTML UIs. In many projects, the backend was implemented still using Java. This caused in bigger projects the development to divide into two different teams - the backend team (using e.g. Java) and the frontend team (using Javascript). Later on, when &lt;a href="https://nodejs.org/en/"&gt;Nodejs&lt;/a&gt; got popular, developers started to implement the backend also using Javascript. Now you could build a modern single-page application using just one programming language, and those developers began to call themselves full-stack developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enter the Cloud Era
&lt;/h3&gt;

&lt;p&gt;Now that we have entered the cloud era in which most applications are running in the cloud, a new competence is required: the ability to create &lt;a href="https://en.wikipedia.org/wiki/Infrastructure_as_code"&gt;Infrastructure as Code&lt;/a&gt;. In many bigger projects, there is a &lt;a href="https://en.wikipedia.org/wiki/DevOps"&gt;DevOps&lt;/a&gt; team that is responsible for the cloud infra (actually a bit of a contradictory term since the whole point of DevOps is that there is no dedicated Ops team). This is because most application developers do not feel comfortable creating IaC solutions - they just want to be application developers (backend, frontend, or both).&lt;/p&gt;

&lt;h3&gt;
  
  
  Real Full-Stack Developer
&lt;/h3&gt;

&lt;p&gt;In my latest project, I’m responsible for creating an IoT platform that can ingest a large number of IoT events and store them in two storages: all events in &lt;a href="https://aws.amazon.com/s3/"&gt;S3&lt;/a&gt; based data lake and the latest events in &lt;a href="https://aws.amazon.com/rds/"&gt;RDS&lt;/a&gt;. I’m also responsible for creating a full-stack application (using &lt;a href="https://clojure.org/"&gt;Clojure&lt;/a&gt; and &lt;a href="https://clojurescript.org/"&gt;Clojurescript&lt;/a&gt;). It’s pretty easy to develop a full-stack app using Clojure/script since you can use the same language in both backend and frontend sides, and &lt;a href="https://github.com/reagent-project/reagent"&gt;Reagent&lt;/a&gt; makes building a modern &lt;a href="https://reactjs.org/"&gt;React-based&lt;/a&gt; single-page application easy.&lt;/p&gt;

&lt;p&gt;I’m the only developer responsible for building the whole system (thankfully, a competent UI designer creates the UI layout design since I have no artistic eye). It is really enjoyable to work on a small green-field project like this - you get to build the cloud infrastructure and the applications running in the infra yourself. When implementing the infrastructure, the backend, and the frontend, you can create a coherent solution and optimize the overall development process. I would say that this is real full-stack development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;Perhaps we should define the concept of full-stack development and a full-stack developer in this new cloud era. You are a full-stack developer if you can create the cloud infrastructure as code and then implement the backend and frontend for your application running in that infra.&lt;/p&gt;

&lt;p&gt;Kari Marttila&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kari Marttila’s Home Page in LinkedIn: &lt;a href="https://www.linkedin.com/in/karimarttila/"&gt;https://www.linkedin.com/in/karimarttila/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cloud</category>
      <category>iac</category>
      <category>productivity</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>Start Learning Cloud Skills!</title>
      <dc:creator>Kari Marttila</dc:creator>
      <pubDate>Thu, 24 Jun 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/karimarttila/start-learning-cloud-skills-4ko6</link>
      <guid>https://dev.to/karimarttila/start-learning-cloud-skills-4ko6</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IvTO0dvY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-04-10-cloud-infrastructure-golden-rules_img_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IvTO0dvY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-04-10-cloud-infrastructure-golden-rules_img_1.png" alt="A screenshot of one of my personal cloud projects"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A screenshot of one of my personal cloud projects.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;In my previous corporation an Azure guy once told me: &lt;em&gt;“It is not possible to be an expert in more than one cloud. If you want to be an expert in Azure you have to focus on Azure.”&lt;/em&gt; A few days passed and an AWS guy told me: &lt;em&gt;“If you want to be an expert in AWS you have to focus on AWS.”&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Is It True?
&lt;/h3&gt;

&lt;p&gt;Is it really true? If you want to be an expert in one cloud, do you really have to ditch other clouds away and focus solely on one cloud? No, it isn’t true. You can happily be an expert in so many clouds you want. I have done IaC in Azure, GCP and AWS projects. There is no law that says you can’t do so. I also have done certifications in all of these clouds (see in &lt;a href="https://www.karimarttila.fi/about/"&gt;www.karimarttila.fi/about&lt;/a&gt; the status of my current cloud certs).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Argument
&lt;/h3&gt;

&lt;p&gt;I guess those guys just were a bit troubled talking to a guy who does multi cloud work. It is very human to try to come up with an argument to rationalize your choices. Their choice was to stick with one cloud - and one of the arguments for that choice was that &lt;em&gt;“it is not possible to be an expert in more than one cloud.”&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  On the Other Hand
&lt;/h3&gt;

&lt;p&gt;On the other hand those guys were right, sort of. If you focus only on one cloud and dedicate all your learning resources to study that cloud - naturally you will be a better expert in that cloud compared to studying two or three clouds.&lt;/p&gt;

&lt;p&gt;But this argument does not fly that far. I have two academic degrees, M.A. in psychology and M.Sc. in Software Engineering. It is perfectly possible to be interested in various fields and technologies and study them all. To study those academic degrees took me a lot more efforts than studying three clouds. The thing that those dedicated cloud guys didn’t get was that I work as a software architect and programmer. I don’t have to remember by heart every little detail of any specific cloud. It’s entirely sufficient to understand the basic concepts well and the most important services. Every cloud provides VPCs, subnets, firewalls, VMs, container registries, Kubernetes as a service, databases etc. If you have learned one cloud it is easier to learn the same basic concepts and how to use them in new clouds, just like with programming languages. No one says that you cannot learn more than one programming language and therefore you have to stick with Javascript (if you don’t believe me, read my story &lt;a href="https://www.karimarttila.fi/languages/2018/11/19/five-languages-five-stories.html"&gt;Five Languages - Five Stories&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Why to Study Cloud Technologies?
&lt;/h3&gt;

&lt;p&gt;I’m old enough to remember the era of the managed services divisions in big IT corporations. At the beginning of the project the software architect had to calculate the needed capacity for various servers since when you sent the order to the managed services division and they started to order those physical servers it took weeks to have them at your data center with all middleware and firewalls installed and configured. In my first cloud project some years ago I was just astonished when I realized that I can create a whole virtual infrastructure with VPC, subnets, firewalls, frontend servers, backend servers, databases, queues, alarms etc using infrastructure code, and without any help from some managed services division. And that I could deploy a new copy of that environment in minutes, up and running, everything configured just as defined in the infrastructure code. Ever since that insight I never looked back. I decided that I will work only in cloud projects in which I can create the cloud infrastructure as code and the applications running in that cloud infra.&lt;/p&gt;

&lt;p&gt;Public clouds with virtual infrastructures are the new paradigm. If you are an application developer I strongly recommend you to start learning cloud technologies as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do You Have to Certify?
&lt;/h3&gt;

&lt;p&gt;No, you don’t. You can learn everything you need to know about public clouds without ever going to one certification exam. The reason I like to take these certification tests is that the requirements for the basic cloud certifications provide really good list for the most important concepts and services you need to understand anyway for being a successful cloud expert in the cloud projects (and I’m talking here the “basic” developer/engineer/architect certs and not the specialty certs). And once you have learned those requirements why not to take the certification exam as well, put the cert in your LinkedIn profile and start getting calls from headhunters.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Start Learning Cloud Skills?
&lt;/h3&gt;

&lt;p&gt;Everyone has his or her own learning methods. Mostly just follow your common sense and start learning the basics whatever methods suites you. There are a lot of material on the Internet. I have liked a lot about the courses in Pluralsight and in Coursera and do the Qwiklabs or other labs related to those courses. I have also attended some traditional courses related to some specifics like Security on AWS.&lt;/p&gt;

&lt;p&gt;If you know nothing of public clouds, just choose one of the “Big Three” - i.e. AWS, Azure, or GCP. It is not that important which one you choose - they are all good public clouds. Once you master the basics in that cloud it is easier to learn the basics regarding the other two clouds as well.&lt;/p&gt;

&lt;p&gt;Your next step is to get &lt;strong&gt;a cloud account&lt;/strong&gt; - you cannot learn cloud skills just by reading books - you absolutely need a cloud account. All Big Three clouds provide “free tier services” or a certain amount of free credits if you order a personal account (at least they used to do that when I learned the basics - nowadays I mostly use my company’s cloud accounts for my personal projects as well and let the capitalists pay my learning bills).&lt;/p&gt;

&lt;p&gt;I worked in my previous corporation as a Cloud Mentor. One of my duties was to teach cloud skills to our developers. I always emphasized that there are three things you need to learn:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Basic information regarding the cloud and its basic services&lt;/strong&gt; (e.g. virtual network, basic storage options, basic computing options).&lt;/li&gt;
&lt;li&gt;How to use the &lt;strong&gt;cloud provider’s portal&lt;/strong&gt; efficiently.&lt;/li&gt;
&lt;li&gt;How to create &lt;strong&gt;infrastructure as code&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can learn the basic concepts and services, and how to use the cloud provider’s portal following various video courses eg. in Pluralsight or Coursera, and do the labs in dedicated sites (like Qwiklabs) or using your personal cloud account (or do like I do: use your company’s cloud account (with permission, of course) and let the capitalist pay your learning bills). But what those courses don’t teach that much is how to create infrastructure as code (IaC) using eg. Terraform or Pulumi or cloud provider’s native IaC tool like CloudFormation, ARM, or Deployment Manager. It is utterly important to learn how to create infrastructure as code since in real projects that is how the cloud solution is done professionally (and not by clicking resources with a mouse in the cloud portal).&lt;/p&gt;

&lt;p&gt;So, what are you waiting for? Start learning cloud skills! (And once you think you are an expert, read: &lt;a href="https://www.karimarttila.fi/iac/2020/04/10/cloud-infrastructure-golden-rules.html"&gt;Cloud Infrastructure Golden Rules&lt;/a&gt; - my personal manifest how to create cloud infrastructures.)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The writer is working at Metosin using Clojure in cloud projects. If you are interested to start a Clojure project in Finland or you are interested to get Clojure training in Finland you can contact me by sending email to my Metosin email address or contact me via LinkedIn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Kari Marttila&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kari Marttila’s Home Page in LinkedIn: &lt;a href="https://www.linkedin.com/in/karimarttila/"&gt;https://www.linkedin.com/in/karimarttila/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cloud</category>
      <category>aws</category>
      <category>azure</category>
    </item>
    <item>
      <title>Comparing Clojure IDEs - Emacs/Cider vs IDEA/Cursive</title>
      <dc:creator>Kari Marttila</dc:creator>
      <pubDate>Wed, 27 Jan 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/karimarttila/comparing-clojure-ides-emacs-cider-vs-idea-cursive-30ga</link>
      <guid>https://dev.to/karimarttila/comparing-clojure-ides-emacs-cider-vs-idea-cursive-30ga</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rSqUPZ98--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rSqUPZ98--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_1.png" alt="IntelliJ IDEA and Cursive and Emacs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;IntelliJ/Cursive and Emacs/Cider editors side by side in a family portrait.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;Recently I edited a blog post in which I interviewed Metosinians regarding their &lt;a href="https://www.metosin.fi/blog/metosin-favorite-editors/"&gt;favorite Clojure editors&lt;/a&gt;. It was quite interesting to see that there is a diverse group of editors in use. In that blog post, I told myself that I have configured my Emacs/Cider setup to be as close to IDEA/Cursive regarding look-and-feel. Someone in Reddit asked about this and I got an idea that I look at my latest Cursive and Dygma related configurations and check if there is some need for fine-tuning regarding my Emacs setup to make both Cursive and Emacs as similar regarding their look-and-feel as possible - and write a new blog post about this experience. There are a couple of earlier blog posts in which I have touched this topic a bit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.karimarttila.fi/clojure/2020/01/06/clojure-impressions-round-three.html"&gt;Clojure Impressions Round Three&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.karimarttila.fi/keyboard/2020/09/28/dygma-raise-reflections-part-1.html"&gt;Dygma Raise Keyboard Reflections Part 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Versions and Repository
&lt;/h3&gt;

&lt;p&gt;I experimented with Cursive and Emacs editors using my latest &lt;a href="https://github.com/karimarttila/clojure/tree/master/statistics/worldstat"&gt;World Statistics exercise&lt;/a&gt; - you can find e.g. the &lt;a href="https://github.com/karimarttila/clojure/blob/master/statistics/worldstat/deps.edn"&gt;deps.edn&lt;/a&gt; (e.g. &lt;code&gt;emacs&lt;/code&gt; profile) and &lt;a href="https://github.com/karimarttila/clojure/blob/master/statistics/worldstat/Justfile"&gt;Justfile&lt;/a&gt; (e.g. commands &lt;code&gt;backend-debug-reveal-kari-port&lt;/code&gt; for starting the repl for Cursive, and &lt;code&gt;backend-debug-kari-emacs&lt;/code&gt; for starting the repl for Emacs). If you are interested in reading about the World Statistics exercise itself, I have another blog post about that: &lt;a href="https://www.karimarttila.fi/clojure/2021/01/19/world-statistics-exercise.html"&gt;World Statistics Exercise&lt;/a&gt; - I just updated the above mentioned deps.edn and Justfile files for this new blog post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Versions&lt;/strong&gt; used in this blog post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IntelliJ IDEA &amp;amp; Cursive: &lt;a href="https://www.jetbrains.com/idea/"&gt;IntelliJ IDEA&lt;/a&gt; 2020.3 Ultimate, &lt;a href="https://cursive-ide.com/"&gt;Cursive&lt;/a&gt; 1.10.0-2020.3, and &lt;a href="https://github.com/nrepl/nrepl"&gt;nrepl&lt;/a&gt; 0.8.2.&lt;/li&gt;
&lt;li&gt;Emacs &amp;amp; Cider: &lt;a href="https://www.gnu.org/software/emacs/"&gt;Emacs&lt;/a&gt; 26.3, &lt;a href="https://github.com/clojure-emacs/cider"&gt;emacs-cider&lt;/a&gt; 1.0.0, and &lt;a href="https://github.com/clojure-emacs/cider-nrepl"&gt;cider-nrepl&lt;/a&gt; 0.25.5.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My workstation is Ubuntu 20.&lt;/p&gt;

&lt;h3&gt;
  
  
  Emacs and Cider
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.gnu.org/software/emacs/"&gt;Emacs&lt;/a&gt; is an old editor - the first versions were created in the 1970’s, the GNU Emacs development started in the 1980’s. I started using Emacs back in my studies at the Helsinki University of Technology in the 1990’s - and have been using Emacs ever since. I really like Emacs, though I’m not by any standard an Emacs or eLisp guru. Emacs is implemented using Lisp (&lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/"&gt;Emacs Lisp&lt;/a&gt;) which makes it a nice editor for writing Lisp code, e.g. Clojure. Having said that I must emphasize that Emacs is a general-purpose editor, not specifically meant for Lisp programming - you can find a so-called &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Major-Modes.html"&gt;Emacs major mode&lt;/a&gt; basically for any programming language. The best way to describe Emacs is that it is an extensible, customizable editor - you can customize it any way you like using the eLisp language. And since eLisp is a Lisp you can add new editing commands while editing. There are also a lot of &lt;a href="https://melpa.org/#/"&gt;Emacs packages&lt;/a&gt; someone has written for you using the eLisp language - the best way to extend Emacs is first to search a melpa package, and if you can’t find one that suits your needs, only then write one for yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/clojure-emacs/cider"&gt;Cider&lt;/a&gt; is an Emacs package that extends Emacs into a full-blown Clojure/script development environment. Using Cider you can do the same REPL wizardry as with any Clojure REPL. The trick actually is that you run a nREPL server and then connect Emacs via Cider to this server - Cider is the nREPL client that communicates with the nREPL server. This way you can send any form inside your Clojure code for evaluation to the nREPL and it sends back the results to Emacs.&lt;/p&gt;

&lt;h3&gt;
  
  
  IntelliJ and Cursive
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.jetbrains.com/idea/"&gt;IntelliJ IDEA&lt;/a&gt; just turned 20 years old, but I haven’t been using IDEA that long. I have been programming with Java and Python for some 20 years, but I started programming Java / Python with Emacs (with Java and Python major modes), and a few years after that started using &lt;a href="https://www.eclipse.org/"&gt;Eclipse&lt;/a&gt; for Java (and continued using Emacs for Python). I used Eclipse quite a few years but at some point, I started using IntelliJ IDEA for both Java and Python programming - kind of nice to use the same editor with the same look-and-feel for both programming languages, and I have stuck with IntelliJ IDEA ever since, though I still use Emacs for various other programming / editing tasks. (Lately, I have also started using &lt;a href="https://code.visualstudio.com/"&gt;Visual Studio Code&lt;/a&gt;, but that’s another story.)&lt;/p&gt;

&lt;p&gt;IntelliJ IDEA is a really good IDE, I really love it. It’s not so bloated as Eclipse, and I really like the layout of the IDE tools more in IDEA than in Eclipse (which I always found difficult to use). &lt;a href="https://cursive-ide.com/"&gt;Cursive&lt;/a&gt; is an IDEA plugin and provides a really enjoyable Clojure programming environment. So, nowadays I can use IDEA for all of my favorite programming languages.&lt;/p&gt;

&lt;p&gt;At this point, I must admit that Clojure is my favorite programming language, no question about it. I used to use Python as a quick scripting language, but with &lt;a href="https://github.com/babashka/babashka"&gt;Babashka&lt;/a&gt; you can write shell scripts also using Clojure without the long start-up time of the JVM. Java is a bit yesterday - I strongly recommend using Clojure in the JVM, and if Clojure is not an option, then &lt;a href="https://kotlinlang.org/"&gt;Kotlin&lt;/a&gt; which is a kind of “Java done right”. Btw, you can read more about my experiences regarding different programming languages in my blog post &lt;a href="https://www.karimarttila.fi/languages/2018/11/19/five-languages-five-stories.html"&gt;Five Languages - Five Stories&lt;/a&gt; and &lt;a href="https://www.karimarttila.fi/languages/2019/12/15/kotlin-much-more-than-just-a-better-java.html"&gt;Kotlin - Much More Than Just a Better Java&lt;/a&gt; - I really should update the “Five stories” blog post into “Six stories” blog post one day.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is a Clojure REPL?
&lt;/h3&gt;

&lt;p&gt;The REPL is the secret weapon of the Lisp world - a way to interact with the Lisp program under development. Other programmers might say that their favorite language also has a “repl” - it’s nothing compared to a real Lisp REPL - you really need a &lt;a href="https://en.wikipedia.org/wiki/Homoiconicity"&gt;homoiconic language&lt;/a&gt; to implement a real powerful REPL, and Lisp (and Clojure) is a homoiconic language.&lt;/p&gt;

&lt;p&gt;A REPL is an acronym for &lt;a href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop"&gt;Read–eval–print loop&lt;/a&gt;. When programming Lisp (and Clojure) a seasoned programmer always keeps a REPL open, and evaluates various forms (the whole namespace, top level form, or some S-expression inside a top level form) in the source code. There are good introductions for the &lt;a href="https://clojure.org/guides/repl/introduction"&gt;Clojure REPL&lt;/a&gt; - I strongly recommend reading them and learn how to use the REPL.&lt;/p&gt;

&lt;p&gt;All right! I guess that’s enough for the introduction. Let’s get into the business.&lt;/p&gt;

&lt;h3&gt;
  
  
  Starting the REPLs for the Editors
&lt;/h3&gt;

&lt;p&gt;For Clojure newbies I explain that there are a couple of ways to use the REPL - either start the repl as part of your IDE, or start an external REPL and connect to it from your editor - I’m using this second way.&lt;/p&gt;

&lt;p&gt;I have in my &lt;a href="https://github.com/karimarttila/clojure/blob/master/statistics/worldstat/deps.edn"&gt;deps.edn&lt;/a&gt; the Clojure, nrepl and cider-nrepl versions defined in the dedicated aliases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{:paths ["resources"]
  :deps {org.clojure/clojure {:mvn/version "1.10.1"}}
  :aliases {
...
    :backend {:extra-paths ["src/clj"]
              :extra-deps {metosin/ring-http-response {:mvn/version "0.9.1"}
...
                          nrepl/nrepl {:mvn/version "0.8.2"}
...
    ;; Emacs Cider specific.
    :emacs {:extra-deps {cider/cider-nrepl {:mvn/version "0.25.5"}}}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;backend&lt;/code&gt; alias was the actual alias for backend development - I created the &lt;code&gt;emacs&lt;/code&gt; alias for this blog post.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/karimarttila/clojure/blob/master/statistics/worldstat/Justfile"&gt;Justfile&lt;/a&gt; provides the commands for starting the repls with the aliases: &lt;code&gt;backend-debug-reveal-kari-port&lt;/code&gt; for starting the repl for Cursive, and &lt;code&gt;backend-debug-kari-emacs&lt;/code&gt; for starting the repl for Emacs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# For Cursive
@backend-debug-reveal-kari-port:
    # clj -J-Dvlaaad.reveal.prefs="{:theme :light}" -M:dev:test:common:backend:reveal:kari -m nrepl.cmdline --middleware '[com.gfredericks.debug-repl/wrap-debug-repl vlaaad.reveal.nrepl/middleware]' -p 44444 -i -C
    clj -M:dev:test:common:backend:reveal:kari -m nrepl.cmdline --middleware '[com.gfredericks.debug-repl/wrap-debug-repl vlaaad.reveal.nrepl/middleware]' -p 44444 -i -C

# Start backend repl with my toolbox for Emacs.
@backend-debug-kari-emacs:
    PROFILE=emacs clj -M:dev:test:common:backend:reveal:kari:emacs -m nrepl.cmdline --middleware '[com.gfredericks.debug-repl/wrap-debug-repl vlaaad.reveal.nrepl/middleware cider.nrepl/cider-middleware]' -p 55555 -i -C

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I created both &lt;a href="https://github.com/casey/just"&gt;Just&lt;/a&gt; recipes for this blog post - the idea is that I added an explicit port for both repls so that I can be sure that Cursive and Emacs are connecting to the respective repls (Cursive port 44444, and Emacs port 55555). The first command has another version with a ligth theme that I tried but didn’t like it that much. In both REPLs I start the nrepl (&lt;code&gt;-m nrepl.cmdline&lt;/code&gt;) and then add some middleware (&lt;code&gt;'[com.gfredericks.debug-repl/wrap-debug-repl vlaaad.reveal.nrepl/middleware]'&lt;/code&gt; , and &lt;code&gt;cider.nrepl/cider-middleware&lt;/code&gt; for Emacs). The first one is a debug helper and the next is a REPL output tool, more about that later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting to the REPLs from the Editors
&lt;/h3&gt;

&lt;p&gt;In &lt;strong&gt;IDEA / Cursive&lt;/strong&gt; create a Run/Debug Configuration as in the picture below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pjyYpAeX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pjyYpAeX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_2.png" alt="Cursive Run Configuration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Emacs / Cider&lt;/strong&gt; give command: &lt;code&gt;cider-connect&lt;/code&gt; and give the localhost and port you used when starting the repl previously (&lt;code&gt;55555&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  The REPL Output in the Editors
&lt;/h3&gt;

&lt;p&gt;The picture below shows the Cursive REPL output window. It is important to mention here that Clojurians do not write in the REPL. You write in the editor and send the forms for evaluation to the REPL, typically using some hotkey (more about that later). So, in the picture below I have first reset my &lt;a href="///clojure/2020/09/07/clojure-integrant-exercise.html"&gt;Integrant&lt;/a&gt; state (using a hotkey, of course). Then I have evaluated the forms (e.g. &lt;code&gt;(set! *print-length* 100)&lt;/code&gt;) one by one with a dedicated hotkey.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NGdE_82H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NGdE_82H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_3.png" alt="Cursive REPL output window"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I actually have three monitors at my table, and I keep my Cursive REPL output window in another monitor than the editor itself. Recently I have been experimenting with the &lt;a href="https://vlaaad.github.io/reveal/"&gt;Reveal&lt;/a&gt; REPL tool and I keep both output windows side by side when experimenting stuff in the Reveal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fUIvbyGO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fUIvbyGO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_4.png" alt="Cursive and Reveal REPL output windows"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s next see the same thing using Emacs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YJUNCfbS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YJUNCfbS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_5.png" alt="Emacs REPL output window"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Emacs, you see 4 buffers. The top buffer shows a Clojure file (one of my scratch files in which I do some experiments). Below that are side by side the cider-repl buffer which is connected to the REPL I described in the previous chapter, and a message buffer next to it (REPL output echoed). At the bottom is the command buffer which also echoes the REPL output. Emacs also nicely echoes the REPL output also in the editor buffer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=&amp;gt; {:country_name "Finland", :country_code :FIN, :series-name "Hospital beds (per 1,000 people)", :series-code :SH.MED.BEDS.ZS, :year 2002, :value 7.4, :country-id 246}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can start the same way the Reveal REPL output tool, as previously (it’s a REPL middleware and not connected to the editors) - I have the Reveal window next to Emacs.&lt;/p&gt;

&lt;p&gt;Now I have explained how to start the REPLs and how to connect to the REPLs using the editors. Let’s next dive into the look-and-feel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Look-And-Feel - the Look
&lt;/h3&gt;

&lt;p&gt;The look is basically the theme of the editor - how colors are used, font ligature, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IDEA/Cursive&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When I started Clojure programming with IDEA/Cursive I wanted to find a light theme that would be nice to my eyes. I have never liked the dark themes that much. I actually thought that there was a Leuven theme in IntelliJ but now that I checked it I noticed that I have created a custom “KariLeuven” theme - possibly used some existing Leuven-like theme as a basis, can’t remember anymore. Anyway, the color theme is rather simple: a light theme with just some coloring (&lt;code&gt;def&lt;/code&gt;, &lt;code&gt;defn&lt;/code&gt; and language macros like &lt;code&gt;comment&lt;/code&gt; using blue, keywords using violet, and strings using green). The picture below shows my theme customizations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dD1xeAFh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dD1xeAFh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_6.png" alt="Cursive Clojure theme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Emacs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I took the initial setup for my Clojure Emacs configuration from &lt;a href="https://github.com/flyingmachine/emacs-for-clojure/"&gt;flyingmachine’s Emacs Cursive setup&lt;/a&gt;. Then I fine-tuned it a bit, e.g. using the Leuven theme which I initially got from &lt;a href="https://github.com/fniessen/emacs-leuven-theme"&gt;fniessen&lt;/a&gt;, and fine-tuned it a bit further.&lt;/p&gt;

&lt;h3&gt;
  
  
  Look-And-Feel - the Feel
&lt;/h3&gt;

&lt;p&gt;The Feel part is how one navigates in the editor and manipulates S-expressions. Since Clojure is a Lisp and therefore a homoiconic language all expressions are so-called &lt;a href="https://en.wikipedia.org/wiki/S-expression"&gt;S-expressions&lt;/a&gt; - S-expressions can be constructed from other S-expressions. That’s why Lisps have a lot of parentheses - but this also makes the language really powerful (macros etc) and nice to edit. There are two major schools related to how to edit Lisp code: the older paredit style and the newer parinfer style. Using paredit you have certain commands that you use to manipulate the S-expressions, e.g. move this S-expression inside the next S-expression, etc. When using parinfer you don’t have to remember any special commands but you achieve the same results by indenting Lisp code.&lt;/p&gt;

&lt;p&gt;Here are a couple of web pages that illustrate the difference nicely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://danmidwood.com/content/2014/11/21/animated-paredit.html"&gt;paredit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shaunlebron.github.io/parinfer/"&gt;parinfer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use paredit myself. I tried parinfer but it felt a bit odd. I have the same hotkeys for slurping and barfing for both IDEA/Cursive and Emacs and it is therefore pretty effortless for me to edit Lisp code using paredit. When &lt;a href="https://www.metosin.fi/blog/metosin-favorite-editors/"&gt;interviewing my colleagues at Metosin&lt;/a&gt; most of the programmers used paredit. But if you are a newcomer to the Lisp / Clojure land I suggest using parinfer - it is easier to start editing the code when you don’t have to learn any special commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hotkeys for Slurping and Barfing
&lt;/h3&gt;

&lt;p&gt;To understand my slurping and barfing hotkeys the reader needs to read my previous blog post &lt;a href="///keyboard/2020/09/28/dygma-raise-reflections-part-1.html"&gt;Dygma Raise Keyboard Reflections Part 1&lt;/a&gt; first. In that blog post, I explain how I have configured the CapsLock key to function as AltGr key in order to use it to get the various parentheses without twisting my right thumb. Then the reader needs to understand the two layers I have configured in my Dygma Raise. I really recommend &lt;a href="https://dygma.com/"&gt;Dygma Raise&lt;/a&gt; - the best keyboard I have ever used - a perfect keyboard for a programmer.&lt;/p&gt;

&lt;p&gt;The following two pictures show my favorite hotkeys in &lt;strong&gt;IDEA/Cursive&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5MtI2Epo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5MtI2Epo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_7.png" alt="Cursive REPL hotkeys"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most used REPL hotkeys are the &lt;code&gt;Integrant reset&lt;/code&gt; = &lt;code&gt;Alt-J&lt;/code&gt; and &lt;code&gt;Send Form Before Caret to REPL&lt;/code&gt; = &lt;code&gt;Alt-L&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then the paredit manipulation hotkeys:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BlMcKuVM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BlMcKuVM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-27-clojure-emacs-vs-cursive_img_8.png" alt="Cursive Paredit hotkeys"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then the same settings in &lt;strong&gt;Emacs&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
;; override the default keybindings in paredit
(eval-after-load 'paredit
  '(progn
     (define-key paredit-mode-map (kbd "C-M-j") nil)
     (define-key paredit-mode-map (kbd "C-M-l") nil)
     (define-key paredit-mode-map (kbd "C-M-j") 'paredit-backward-slurp-sexp)
     (define-key paredit-mode-map (kbd "M-&amp;lt;right&amp;gt;") 'paredit-forward-slurp-sexp)
     (define-key paredit-mode-map (kbd "C-M-l") 'paredit-backward-barf-sexp)
     (define-key paredit-mode-map (kbd "M-&amp;lt;left&amp;gt;") 'paredit-forward-barf-sexp)
     (define-key paredit-mode-map (kbd "C-&amp;lt;right&amp;gt;") 'right-word)
     (define-key paredit-mode-map (kbd "C-&amp;lt;left&amp;gt;") 'left-word)
     ))
...
(eval-after-load 'cider
  '(progn
...
     (define-key cider-mode-map (kbd "M-l") 'cider-eval-last-sexp)
     (define-key cider-mode-map (kbd "M-ö") 'cider-eval-defun-at-point)
     (define-key cider-mode-map (kbd "M-n") 'cider-repl-set-ns)
     (define-key cider-mode-map (kbd "M-m") 'cider-load-buffer)
     (define-key cider-mode-map (kbd "M-{") 'cider-format-buffer)
     (define-key cider-mode-map (kbd "M-å") 'cider-test-run-ns-tests)
     (define-key cider-mode-map (kbd "M-ä") 'cider-test-run-test)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see it is pretty simple to configure the same REPL hotkeys and the same paredit hotkeys for both IDEA/Cursive and Emacs/Cider. When reading this you have to remember that I’m not using arrow keys but my arrow keys are defined as CapsLock + i/j/k/l, so when I’m forward barfing I actually have my left little finger in CapsLock and my left thumb in one of the Dygma thumb keys (which is Alt) and hit &lt;code&gt;J&lt;/code&gt; with my right index finger. This may sound very complicated, but actually, it isn’t - I have considered various layouts and the current layout is a kind of evolutionary result of my keyboard layout experiments. I really like the system since it resembles a bit playing the classical guitar: I hit the “control” (ctrl, shift, alt, CapsLock) key combinations using my left hand, and the navigation (arrow), manipulation (barfing/slurping), and evaluation (REPL) keys with my right hand. All these key combinations are in my muscle memory and I don’t consciously think about them - I just edit code. Even though this system suits me nicely I wouldn’t recommend it to someone else - you have to experiment and find your own keyboard, keyboard layout, and finally the hotkeys for your favorite programming language in that layout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;Both IDEA/Cursive and Emacs/Cider are excellent editors and Clojure integrated development environments. If you want to switch from one to another you can quite easily configure both editors to have pretty much the same look-and-feel.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The writer is working at Metosin using Clojure in cloud projects. If you are interested to start a Clojure project in Finland or you are interested to get Clojure training in Finland you can contact me by sending email to my Metosin email address or contact me via LinkedIn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Kari Marttila&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kari Marttila’s Home Page in LinkedIn: &lt;a href="https://www.linkedin.com/in/karimarttila/"&gt;https://www.linkedin.com/in/karimarttila/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>clojure</category>
      <category>cursive</category>
      <category>emacs</category>
    </item>
    <item>
      <title>World Statistics Exercise</title>
      <dc:creator>Kari Marttila</dc:creator>
      <pubDate>Tue, 19 Jan 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/karimarttila/world-statistics-exercise-22lb</link>
      <guid>https://dev.to/karimarttila/world-statistics-exercise-22lb</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--J9cZ4unb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-19-world-statistics-exercise_img_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--J9cZ4unb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-19-world-statistics-exercise_img_1.png" alt="World Statistics App"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;World Statistics App.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;After implementing my web-store exercise using six languages (Clojure, Python, Javascript, Java, Go and Kotlin) and with 5 data stores (CSV, DynamoDB, Azure Tables, PostgreSQL, and Datomic, read more about in my blog posts, e.g. &lt;a href="https://www.karimarttila.fi/languages/2018/11/19/five-languages-five-stories.html"&gt;Five Languages - Five Stories&lt;/a&gt; ) I was pretty much fed up with that exercise. I wanted to do something totally different. But what? I spent quite a lot of time figuring out what to do. I was dreaming to do something with &lt;a href="https://www.avoindata.fi/fi"&gt;Finnish Open data&lt;/a&gt; that could benefit the general public. I was browsing the Finnish Open data but couldn’t figure out anything useful that would contain a lot of data points. Then I decided to take a look at the &lt;a href="https://data.worldbank.org/"&gt;World Bank Open data&lt;/a&gt;. Just out of curiosity I downloaded various metrics between the years 2002-2017, some 150.000 data points altogether. Then I just started to experiment with the data using the Clojure REPL - a great tool for studying and experimenting with data. Little by little a new exercise was formed in my head: Implement a full-stack app using Clojure and ClojureScript to filter and enrich the data and visualize the data sets using a frontend. This blog post depicts the story of this exercise.&lt;/p&gt;

&lt;p&gt;The application source can be found in my &lt;a href="https://github.com/karimarttila/clojure"&gt;Github clojure repo&lt;/a&gt; in directory &lt;a href="https://github.com/karimarttila/clojure/tree/master/statistics/worldstat"&gt;statistics/worldstat&lt;/a&gt;. The application is live in &lt;a href="https://km-worldstat.herokuapp.com/worldstat/index.html"&gt;Heroku&lt;/a&gt; (as long as the Heroku free tier dyno hours have not run out). NOTE: If there are still dyno hours and no-one has used the app for some time you may need to wait for a while for Heroku to start the app. Since the app is running in the free tier there might be other aspects as well that may impact using the app (max connections etc.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Technology Overview
&lt;/h3&gt;

&lt;p&gt;I used &lt;a href="https://clojure.org/"&gt;Clojure&lt;/a&gt;, and some Clojure libraries (like &lt;a href="https://github.com/metosin/reitit"&gt;reitit&lt;/a&gt;) to implement the backend. The backend reads the data from CSV files that I downloaded from the Worldbank site, and manipulates the data so that we have the data and meta-data nicely as Clojure data structures stored as part of the &lt;a href="https://github.com/weavejester/integrant"&gt;Integrant&lt;/a&gt; state. I used &lt;a href="https://github.com/MastodonC/kixi.stats"&gt;kixi&lt;/a&gt; as the statistics library.&lt;/p&gt;

&lt;p&gt;I used &lt;a href="https://clojurescript.org/"&gt;ClojureScript&lt;/a&gt;, &lt;a href="https://reagent-project.github.io/"&gt;Reagent&lt;/a&gt;, and &lt;a href="https://day8.github.io/re-frame/"&gt;Re-Frame&lt;/a&gt; to implement the frontend. For frontend development I used &lt;a href="https://github.com/thheller/shadow-cljs"&gt;shadow-cljs&lt;/a&gt;. For CSS I used the excellent and light CSS library &lt;a href="https://bulma.io/"&gt;Bulma&lt;/a&gt;. For data visualization I used &lt;a href="https://vega.github.io/vega-lite/"&gt;Vega Lite&lt;/a&gt; and as a Clojure wrapper for Vega I used Metasoarous’ &lt;a href="https://github.com/metasoarous/oz"&gt;Oz&lt;/a&gt; library. The &lt;a href="https://github.com/vega/vega/blob/master/packages/vega-loader/test/data/world-110m.json"&gt;world-110m.json&lt;/a&gt; world map is from Vega.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend - Creating the Data
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://github.com/karimarttila/clojure/blob/master/statistics/worldstat/src/clj/worldstat/backend/main.clj"&gt;main.clj&lt;/a&gt; I read the Integrant configuration: the data files (the CSV files that I downloaded from the Worldbank, and the toposon file) and inject them to &lt;a href="https://github.com/karimarttila/clojure/blob/master/statistics/worldstat/src/clj/worldstat/backend/data/init.clj"&gt;init.clj&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defmethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ig/init-key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:backend/data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;data-files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;topojson-file&lt;/span&gt;&lt;span class="p"&gt;]}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;init/get-data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data-files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;topojson-file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;a href="https://github.com/karimarttila/clojure/blob/master/statistics/worldstat/src/clj/worldstat/backend/data/init.clj"&gt;init.clj&lt;/a&gt; I parse the data from the CSV files and convert it to &lt;a href="https://github.com/edn-format/edn"&gt;edn&lt;/a&gt;. In that file I do some processing and finally return the data structure back to main.clj which stores the data as part of the Integrant state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get-data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;data-files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;topojson-file&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ENTER get-data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;topojson-country-codes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;read-topojson-country-codes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;topojson-file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;juxt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:acr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;topojson-country-codes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;data-points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-data-points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data-files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;countries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;years&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-metadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data-points&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:raw-points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data-points&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;remove-non-country-values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data-points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;non-country-codes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:countries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;countries&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:series&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:years&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;years&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:country-codes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;country-codes-to-names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:series-codes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;series-codes-to-names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:non-country-codes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;non-country-codes&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:country-ids&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:topojson-country-codes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;topojson-country-codes&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An important part of this processing is to store the topojson country ids as part of the data points so that we can later use them as hooks to actual countries in the topojson map.&lt;/p&gt;

&lt;p&gt;I downloaded some 150.000 data points for this exercise (&lt;code&gt;:raw-points&lt;/code&gt;). I removed various data points (&lt;code&gt;:points&lt;/code&gt;) that were associated to continents, income groups, etc. Finally, I had some 130.000 data points that were associated to actual countries found in the topojson map. It’s easy to study the data using the Clojure REPL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:raw-points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;user/data&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;153152&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;user/data&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;129824&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;… but more about that in the next chapter.&lt;/p&gt;

&lt;p&gt;An individual data point is a Clojure map. Let’s get one data point from Finland and check it in more detail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;transduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-filter/filter-by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:country_code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:FIN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;conj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-world/get-world-data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;user/env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:SH.MED.BEDS.ZS&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:country_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Finland"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:country_code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:FIN,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:series-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Hospital beds (per 1,000 people)"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:series-code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:SH.MED.BEDS.ZS,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:year&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2002&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.4&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:country-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;246&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Backend - Using Clojure REPL to Experiment with the Data
&lt;/h3&gt;

&lt;p&gt;Using a real REPL is such a joy with Clojure. Let’s use it to experiment with the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;*print-length*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; So that we don't accidentally list 150.000 data points in repl...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; Require some namespaces.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;worldstat.backend.data.world&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ws-world&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;worldstat.common.data.filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ws-filter&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kixi.stats.core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kixi&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; Check how many points there are...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:raw-points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;user/data&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;153152&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;user/data&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;129824&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; What keys do we have in our data?&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;user/data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:country-codes&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:raw-points&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:series&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:non-country-codes&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:country-ids&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:points&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:series-codes&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:countries&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:topojson-country-codes&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:years&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; Let's sort countries...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sort-by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:country_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:countries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;user/data&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="no"&gt;:country_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Afghanistan"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:country_code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:AFG,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:country-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:country_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Albania"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:country_code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:ALB,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:country-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:country_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Algeria"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:country_code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:DZA,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:country-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:country_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"American Samoa"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:country_code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:ASM,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:country-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; Get hospital beds, filter data points belonging to Finland, and get first data point. &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;transduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-filter/filter-by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:country_code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:FIN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;conj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-world/get-world-data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;user/env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:SH.MED.BEDS.ZS&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:country_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Finland"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:country_code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:FIN,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:series-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Hospital beds (per 1,000 people)"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:series-code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:SH.MED.BEDS.ZS,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:year&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2002&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.4&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:country-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;246&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; Let's check some basic statistics, mean and standard deviation for certain data. &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;transduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kixi/mean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-world/get-world-data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;user/env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:SH.MED.BEDS.ZS&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.668379451410944&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;transduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kixi/standard-deviation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-world/get-world-data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;user/env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:SH.MED.BEDS.ZS&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.601440577329428&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repl is truly such a powerful tool in Lisp. I always keep a repl scratch file in all my Clojure projects and I do experimentation like above in those scratch files - it’s nice to have those experimentations later on as a historical record of how I implemented functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend - API
&lt;/h3&gt;

&lt;p&gt;I used the excellent Metosin &lt;a href="https://github.com/metosin/reitit"&gt;reitit&lt;/a&gt; library for routing: &lt;a href="https://github.com/karimarttila/clojure/blob/master/statistics/worldstat/src/clj/worldstat/backend/routes/routes.clj"&gt;routes.clj&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"/data"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:swagger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:tags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"/metric/:metric"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:summary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"metric"&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="no"&gt;:parameters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:metric&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;]]}&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="no"&gt;:responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="no"&gt;:handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                                          &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:parameters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:metric&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;world/get-world-data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;keyword&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
                                                &lt;/span&gt;&lt;span class="n"&gt;r/ok&lt;/span&gt;&lt;span class="p"&gt;)))}}]&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"/countries"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:summary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"countries"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;world/get-countries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                                     &lt;/span&gt;&lt;span class="n"&gt;r/ok&lt;/span&gt;&lt;span class="p"&gt;))}}]&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"/years"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:summary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"years"&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="no"&gt;:responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="no"&gt;:handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;world/get-years&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                                     &lt;/span&gt;&lt;span class="n"&gt;r/ok&lt;/span&gt;&lt;span class="p"&gt;))}}]&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"/metric-names"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:summary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"metric-names"&lt;/span&gt;&lt;span class="w"&gt;
                             &lt;/span&gt;&lt;span class="no"&gt;:responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
                             &lt;/span&gt;&lt;span class="no"&gt;:handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                                        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;world/get-metric-names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="n"&gt;r/ok&lt;/span&gt;&lt;span class="p"&gt;))}}]]]])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, you can query the actual data points by giving a metric (&lt;code&gt;:series-code&lt;/code&gt;) and the backend returns the data points for that metric. Basically, I just implemented a minimum API that is needed by the frontend to do the visualization.&lt;/p&gt;

&lt;p&gt;Reitit library provides swagger documentation out of the box, so you can experiment with the API easily also with the browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ytZ4nSOV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-19-world-statistics-exercise_img_2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ytZ4nSOV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-19-world-statistics-exercise_img_2.png" alt="Swagger"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;API Swagger documentation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next, let’s move to the frontend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend - Reagent and Re-frame
&lt;/h3&gt;

&lt;p&gt;I used &lt;a href="https://clojurescript.org/"&gt;ClojureScript&lt;/a&gt;, &lt;a href="https://reagent-project.github.io/"&gt;Reagent&lt;/a&gt;, and &lt;a href="https://day8.github.io/re-frame/"&gt;Re-Frame&lt;/a&gt; to implement the frontend. For frontend development I used &lt;a href="https://github.com/thheller/shadow-cljs"&gt;shadow-cljs&lt;/a&gt;. Reagent provides a nice interface to &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt;, and Re-frame is ClojureScript framework, that uses Reagent and provides simple state management to build single page applications using Clojurescript. All this tooling works pretty seamlessly with the Clojure backend repl so that when I refresh the Integrant state (with one hot key, of course) the frontend also refreshes. One can get a Clojurescript repl and connect to the frontend application running inside the browser, if you need it. Usually you don’t, since there are nice tools and tricks to examine the frontend state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sDc_fVFC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-19-world-statistics-exercise_img_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sDc_fVFC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-19-world-statistics-exercise_img_3.png" alt="Clojurescript frontend tools"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Clojurescript frontend tools.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On the right side of the browser, you see the standard Chrome developer tools window. There you can examine the html structure, see console log, etc, nothing new for any frontend developer.&lt;/p&gt;

&lt;p&gt;At the bottom is the Metosin &lt;a href="https://github.com/metosin/reagent-dev-tools"&gt;reagent-dev-tools&lt;/a&gt;. You can subscribe to all re-frame application data there and browse the data. I also implemented a simple debug panel in which I show some most important application data that I used when implementing the frontend. Btw. I really recommend using the Metosin reagent-dev-tools. E.g. I’m in a customer project in which we are implementing a rather complex Clojure full stack application and there is really complex business logic - we have various fixtures in the reagent-dev-tools which makes it a breeze to initialize the frontend and the whole application to a certain state in the middle of the business logic.&lt;/p&gt;

&lt;p&gt;All this tooling works really nicely. I’m an old backend programmer myself, but using these tools it was quite effortless to implement this full stack application. You don’t have to be a Javascript + React guru to implement a frontend like the one in this exercise.&lt;/p&gt;

&lt;h3&gt;
  
  
  CSS - Bulma
&lt;/h3&gt;

&lt;p&gt;I used &lt;a href="https://bulma.io/"&gt;Bulma&lt;/a&gt; as a CSS framework. Bulma is really great - I barely added any custom css in this application. Let’s see part of the frontend using the &lt;a href="https://github.com/weavejester/hiccup"&gt;hiccup&lt;/a&gt; notation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div.row&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div.columns&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div.column.is-9&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;oz/vega-lite&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-data/world-schema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selected-year&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selected-metric-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-util/vega-debug&lt;/span&gt;&lt;span class="p"&gt;)]]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div.column.auto&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;country-stats&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div.rows&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div.row&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:p.is-size-3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selected-country-name&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div.row&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:p.is-size-5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Min: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-util/one-decimal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:min&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;country-stats&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:p.is-size-5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Max: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-util/one-decimal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;country-stats&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:p.is-size-5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Mean: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-util/one-decimal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:mean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;country-stats&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:p.is-size-5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Std.dev: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-util/one-decimal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:standard-deviation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;country-stats&lt;/span&gt;&lt;span class="p"&gt;))]]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div.row&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;oz/vega-lite&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-data/country-year-diagram&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selected-metric-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selected-country-code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-util/vega-debug&lt;/span&gt;&lt;span class="p"&gt;)]]]&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:p.is-size-4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Click a country!"&lt;/span&gt;&lt;span class="p"&gt;])]]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div.row&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"(Missing data for country is shown with color gray)"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div.row&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Read more about this app in my blog "&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:href&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"https://www.karimarttila.fi/clojure/2021/01/19/world-statistics-exercise.html"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"www.karimarttila.fi"&lt;/span&gt;&lt;span class="p"&gt;]]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use Bulma styles seamlessly with hiccup, e.g. &lt;code&gt;:div.columns&lt;/code&gt; tells the browser to render the div using &lt;a href="https://bulma.io/documentation/columns/"&gt;Bulma columns&lt;/a&gt; etc. Check more in the &lt;a href="https://bulma.io/"&gt;Bulma&lt;/a&gt; documentation.&lt;/p&gt;

&lt;p&gt;The dropdown box for selecting the metric and the slider for selecting the year to visualize in the map are also Bulma components. You can customize the Bulma components quite nicely using Clojurescript, example showing the year slider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;slider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;slider-value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;r/atom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div.slider-content&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:input.slider.is-fullwidth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:step&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:min&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;slider-value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt;
                                  &lt;/span&gt;&lt;span class="no"&gt;:on-change&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                                               &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.preventDefault&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                                               &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;new-value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.-value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.getElementById&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;js/document&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;&lt;span class="w"&gt;
                                                 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reset!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;slider-value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new-value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                                                 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/dispatch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;::ws-state/select-year&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;js/parseInt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new-value&lt;/span&gt;&lt;span class="p"&gt;)])))}]]))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, basically I just take the current value of the slider and store it in the re-frame database: &lt;code&gt;(re-frame/dispatch [::ws-state/select-year (js/parseInt new-value)])&lt;/code&gt; .&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend - Creating the Map
&lt;/h3&gt;

&lt;p&gt;For data visualization, I used &lt;a href="https://vega.github.io/vega-lite/"&gt;Vega Lite&lt;/a&gt;, and as a Clojure wrapper for Vega, I used Metasoarous’ &lt;a href="https://github.com/metasoarous/oz"&gt;Oz&lt;/a&gt; library. The &lt;a href="https://github.com/vega/vega/blob/master/packages/vega-loader/test/data/world-110m.json"&gt;world-110m.json&lt;/a&gt; world map is from Vega.&lt;/p&gt;

&lt;p&gt;I think I spent most of the time developing this application by tuning the world map. But it was really fun to experiment with how to visualize data and how to interact with the map. The result of this experiment can be seen in file &lt;a href="https://github.com/karimarttila/clojure/blob/master/statistics/worldstat/src/cljs/worldstat/frontend/data.cljs"&gt;data.cljs&lt;/a&gt;, I highlight here the most important part of the map configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;world-schema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;metric-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:schema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"https://vega.github.io/schema/vega/v5.json"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="no"&gt;:autosize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="no"&gt;:width&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="no"&gt;:height&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="no"&gt;:scales&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"color"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="no"&gt;:type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"quantize"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;             
   &lt;/span&gt;&lt;span class="no"&gt;:marks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"shape"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="no"&gt;:from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"graticule"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="no"&gt;:encode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:strokeWidth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"shape"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="no"&gt;:from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"world"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="no"&gt;:encode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:enter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:tooltip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:signal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"datum.value === null ? {'country': datum.country_name, 'value': 'Missing data'} : {'country': datum.country_name, 'value': format(datum.value, '.2f')}"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                             &lt;/span&gt;&lt;span class="no"&gt;:href&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:field&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"url"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"nominal"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="c1"&gt;;; If missing data show as grey.&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="no"&gt;:update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"datum.value === null"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"gray"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                                     &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:scale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"color"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"field"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;}]}&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="no"&gt;:hover&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"red"&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="no"&gt;:transform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"geoshape"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="no"&gt;:data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"countries"&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="no"&gt;:values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;transduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-filter/filter-by-year&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;conj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"world"&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="no"&gt;:url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"data/world-110m.json"&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="no"&gt;:format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"topojson"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:feature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"countries"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="no"&gt;:transform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"lookup"&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="no"&gt;:from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"countries"&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="no"&gt;:key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"country-id"&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="no"&gt;:fields&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="no"&gt;:values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"country_name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"country_code"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
                       &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"formula"&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="no"&gt;:from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"countries"&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="no"&gt;:expr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"'#/country/' + datum.country_code"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;}]}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"graticule"&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="no"&gt;:transform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"graticule"&lt;/span&gt;&lt;span class="p"&gt;}]}]})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, the Vega interface provides quite extensive ways to inject data to the Vega component and how the component should transform, interpret and visualize the data.&lt;/p&gt;

&lt;p&gt;Let’s spend some time with this configuration since it is the actual beef of this app.&lt;/p&gt;

&lt;p&gt;First I inject to the map configuration the data (points, year, metric-name, min and max of the points): &lt;code&gt;(defn world-schema [points year metric-name min max]&lt;/code&gt;. Then I transform the data points using a join (lookup) between the topojson map data and my data points using the &lt;code&gt;id&lt;/code&gt; of the topojson data and &lt;code&gt;country-id&lt;/code&gt; of my data points as joining the data. In this join I inject the corresponding country_name and country_code to the new data set so that I can use the &lt;code&gt;country_name&lt;/code&gt; to provide the country name when the user hovers the mouse in certain country. I use the &lt;code&gt;country_code&lt;/code&gt; for providing a hook when the user mouse clicks a certain country: &lt;code&gt;:expr "'#/country/' + datum.country_code" :as "url"}]}&lt;/code&gt; and &lt;code&gt;:href {:field "url" :type "nominal"}&lt;/code&gt; . That’s basically what you have to do - and the Vega library does the rest. Then I just show the map component in the application home page, &lt;a href="https://github.com/karimarttila/clojure/blob/master/statistics/worldstat/src/cljs/worldstat/frontend/main.cljs"&gt;main.cljs&lt;/a&gt; (injecting the re-frame subscribed data):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;selected-metric-code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selected-metric-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/subscribe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;::ws-state/current-metric&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;selected-year&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/subscribe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;::ws-state/current-year&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;selected-country-code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selected-country-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/subscribe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;::ws-state/current-country&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;standard-deviation&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/subscribe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;::ws-state/world-data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selected-metric-code&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;metric-names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/subscribe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;::ws-state/metric-names&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;if-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/dispatch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;::ws-state/get-world-data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selected-metric-code&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;country-stats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selected-country-code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-data/get-stats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selected-country-code&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div.column.is-9&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;oz/vega-lite&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-data/world-schema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selected-year&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selected-metric-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-util/vega-debug&lt;/span&gt;&lt;span class="p"&gt;)]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Vega Editor
&lt;/h3&gt;

&lt;p&gt;Since the map component is a bit complicated and for a Vega newcomer the transform operations are a bit hairy Vega provides excellent development tools. See the three dots icon here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ieiMRhM---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-19-world-statistics-exercise_img_4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ieiMRhM---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-19-world-statistics-exercise_img_4.png" alt="Going to Vega Editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Going to Vega Editor.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Just click it and choose the &lt;code&gt;Open in Vega Editor&lt;/code&gt; and the browser opens this for you:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--77W7gh1_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-19-world-statistics-exercise_img_5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--77W7gh1_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2021-01-19-world-statistics-exercise_img_5.png" alt="Vega Editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Vega Editor.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can experiment with the graphics first using the Vega editor, see the data, and check how various tunings in parameters look like in the map - and once you are happy just copy-paste the configuration back to your Clojurescript source file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend - Navigation etc.
&lt;/h3&gt;

&lt;p&gt;There is very little navigation in the application. I used Reitit also to do the frontend navigation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;routes-dev&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;::ws-state/home&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:view&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;home-page&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:link-text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Home"&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:controllers&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-log/clog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Entering home page, params: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:stop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-log/clog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Leaving home page, params: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)))}]}]&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"country/:country-code"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;::ws-state/country&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:parameters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:country-code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:view&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;home-page&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:link-text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"country"&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:controllers&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-log/clog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Entering country, params: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:stop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-log/clog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Leaving country, params: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)))}]}]])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, the only navigation is provided when the user clicks a country - we show the year statistics of the selected metric in the line diagram. I actually spend some time with this functionality. I couldn’t figure out how to register the mouse button click event in the vega map - but I found in the documentation a way to inject a href as part of the map, so I used it: &lt;code&gt;:expr "'#/country/' + datum.country_code" :as "url"}]}&lt;/code&gt; and &lt;code&gt;:href {:field "url" :type "nominal"}&lt;/code&gt; - then in the frontend routing I just provide a route for this uri: &lt;code&gt;["country/:country-code"&lt;/code&gt;, and in &lt;a href="https://github.com/karimarttila/clojure/blob/master/statistics/worldstat/src/cljs/worldstat/frontend/state.cljs"&gt;state.cljs&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/reg-event-db&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;::navigated&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new-match&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;old-match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:current-route&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;new-path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new-match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;country-code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new-match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:parameters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:country-code&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;country-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-country-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;country-code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;controllers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rfc/apply-controllers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:controllers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;old-match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new-match&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-log/clog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"::state/navigated, new-path: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new-path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ws-log/clog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"::state/navigated, country-code: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;country-code&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cond-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;assoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:current-route&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;assoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new-match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:controllers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;controllers&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="n"&gt;country-code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;assoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:current-country&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;country-code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;country-name&lt;/span&gt;&lt;span class="p"&gt;})))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, here we just get the &lt;code&gt;country-code&lt;/code&gt; from the uri, find the matching &lt;code&gt;country-name&lt;/code&gt; for it and store them into the re-frame database for further use.&lt;/p&gt;

&lt;p&gt;The full stack app is now ready. Let’s next wrap the app into a deployment unit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Container and Heroku Deployment
&lt;/h3&gt;

&lt;p&gt;I considered deploying the app to AWS but since I’m pretty stingy I didn’t want to spend my personal money on that. Then I realized that I can use &lt;a href="https://www.heroku.com/free"&gt;Heroku Free Tier&lt;/a&gt; to deploy the app to the cloud (at least for the time the Heroku free dyno hours run out, or the World Bank wants to start sponsoring the app :-) ).&lt;/p&gt;

&lt;p&gt;First I needed to create a docker container. Instructions for creating and running the Docker container (basically just testing that it works, Heroku creates the image using the heroku cli, more about it later):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;just uberjar&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker build -f Dockerfile . -t worldstat:latest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker run -e PORT=9999 -it -p9999:9999 worldstat:latest&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/karimarttila/clojure/blob/master/statistics/worldstat/Dockerfile"&gt;Dockerfile&lt;/a&gt; is pretty minimalistic. I just create a non-root user to run the app (Heroku requires this), I copy the app and the data to the image, and provide the entrypoint to the application: &lt;a href="https://github.com/karimarttila/clojure/blob/master/statistics/worldstat/docker/run-app.sh"&gt;run-app.sh&lt;/a&gt;. Notice the pretty harsh memory limits in the app (Heroku gives just max 512MB memory in the free tier).&lt;/p&gt;

&lt;p&gt;NOTE: The application is running in the Heroku free tier and Heroku removes the application if it is not used for a certain period of time - in this case just wait patiently a few seconds and you should see the app unless the dyno hours are completely spent.&lt;/p&gt;

&lt;p&gt;The Heroku deployment process for Docker images is pretty nicely documented &lt;a href="https://devcenter.heroku.com/articles/container-registry-and-runtime"&gt;here&lt;/a&gt;. Let’s give short instructions if you want to try it yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First build the uberjar: &lt;code&gt;just uberjar&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Then basically just follow the instructions in the &lt;a href="https://devcenter.heroku.com/articles/container-registry-and-runtime"&gt;Heroku documentation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Create a &lt;a href="https://www.heroku.com/"&gt;Heroku account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;heroku create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;heroku container:login&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;heroku container:push web -a &amp;lt;YOUR-HEROKU-APP-NAME&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;heroku container:release web -a &amp;lt;YOUR-HEROKU-APP-NAME&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I then changed the application name to some more descriptive: &lt;a href="https://km-worldstat.herokuapp.com/worldstat/index.html"&gt;km-worldstat&lt;/a&gt; .&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;This exercise was really interesting. I learned quite a lot about how to experiment data using REPL in the backend. I also learned quite a lot how to create nice visualizations using the Vega library. I also learned that using Clojure and Clojurescript you can create quite powerful full stack applications without the need being a Javascript / React guru.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The writer is working at Metosin using Clojure in cloud projects. If you are interested in starting a Clojure project or getting Clojure training in Finland you can contact me by sending an email to my Metosin email address or contact me via LinkedIn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Kari Marttila&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kari Marttila’s Home Page in LinkedIn: &lt;a href="https://www.linkedin.com/in/karimarttila/"&gt;https://www.linkedin.com/in/karimarttila/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>clojure</category>
      <category>clojurescript</category>
      <category>repl</category>
    </item>
    <item>
      <title>Malli Clojure Library</title>
      <dc:creator>Kari Marttila</dc:creator>
      <pubDate>Mon, 07 Dec 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/karimarttila/malli-clojure-library-4gac</link>
      <guid>https://dev.to/karimarttila/malli-clojure-library-4gac</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YfIvqpUp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-12-07-malli-clojure-library_img_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YfIvqpUp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-12-07-malli-clojure-library_img_1.png" alt="IntelliJ IDEA and Cursive"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Using &lt;code&gt;malli&lt;/code&gt; library to validate POST body data in the REST API.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;At &lt;a href="https://www.metosin.fi/en/"&gt;Metosin&lt;/a&gt; we use our own libraries quite a lot in our projects - therefore a good reason to learn to use these libraries. For learning to use the &lt;a href="https://github.com/metosin/malli"&gt;malli&lt;/a&gt; library I experimented with it using the Clojure REPL, and I also added malli validation to my previous &lt;code&gt;re-frame&lt;/code&gt; exercise that can be found in my &lt;a href="https://github.com/karimarttila/clojure"&gt;Clojure&lt;/a&gt; repo, in directory &lt;a href="https://github.com/karimarttila/clojure/tree/master/webstore-demo/re-frame-demo"&gt;re-frame&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I did most of the experimentation in my REPL scratch file (not included in the repo). I then wrote the observations regarding this learning project in this blog post mostly for myself - verbalizing what one has discovered is a good learning method.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Malli?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/metosin/malli"&gt;malli&lt;/a&gt; is a Clojure/Script library for data validation and specification. It provides tools for validation, coercion, and other aspects related to the question of whether the data in hand is valid for your purposes. &lt;code&gt;malli&lt;/code&gt; also integrates nicely with the &lt;a href="https://github.com/metosin/reitit"&gt;reitit&lt;/a&gt; routing library providing nice tools to validate and coerce data coming to the REST API.&lt;/p&gt;

&lt;p&gt;If you are interested to learn more about &lt;code&gt;malli&lt;/code&gt; I list below some resources I used myself while experimenting with &lt;code&gt;malli&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/metosin/malli"&gt;malli Github repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.metosin.fi/blog/malli/"&gt;Tommi Reiman’s blog post regarding malli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=ww9yR_rbgQs"&gt;Data-driven Rapid Application Development with Malli&lt;/a&gt; in Youtube&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=MR83MhWQ61E"&gt;Inside Data-driven Schemas presentation&lt;/a&gt; in Youtube&lt;/li&gt;
&lt;li&gt;&lt;a href="https://malli.io"&gt;malli.io playground&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My colleague &lt;a href="https://miikka.me/"&gt;Miikka Koskinen&lt;/a&gt; has written a couple of excellent blog posts regarding &lt;code&gt;malli&lt;/code&gt; and other data validation libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://quanttype.net/posts/2020-05-03-schema-spec-and-malli.html"&gt;Schema, Spec, and Malli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://quanttype.net/posts/2020-05-10-essential-features.html"&gt;Essential features of data specification libraries&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Data Validation?
&lt;/h3&gt;

&lt;p&gt;Clojure is a dynamic language and there is no static type checking by the Clojure compiler. Some developers see this as a downside but it actually makes Clojure both simple and also very expressive and powerful regarding how to manipulate data. One could say that in Clojure we don’t have types - we have something better: data. You can do a lot more with data than with types. And if you are interested in whether your data conforms to a certain data shape - you have validation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Malli, Spec, or Schema?
&lt;/h3&gt;

&lt;p&gt;There are three major data validation libraries in the Clojure ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/plumatic/schema"&gt;schema&lt;/a&gt;: older data validation library which was the de facto schema library before &lt;code&gt;spec&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://clojure.org/about/spec"&gt;spec&lt;/a&gt;: the data validation library that comes with Clojure 1.9 or higher and is also used to describe the Clojure core language and Clojure library apis.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/metosin/malli"&gt;malli&lt;/a&gt;: the data validation library provided by &lt;a href="https://www.metosin.fi/en/"&gt;Metosin&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tried to verbalize to myself what is the purpose and strengths of these libraries, their sweet spots in projects, and how they differ from each other. I wrote about this blog post in the Metosin Slack, and one of my colleagues hinted that our colleague Miikka Koskinen had already written a blog post that does a very good job describing these libraries, I really recommend reading it: &lt;a href="https://quanttype.net/posts/2020-05-03-schema-spec-and-malli.html"&gt;Schema, Spec, and Malli&lt;/a&gt;. So, I’m not going to describe the differences between those data validation libraries - Miikka has already done a much better job describing them than I ever could have done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Experimenting with Malli Using the Clojure REPL
&lt;/h3&gt;

&lt;p&gt;It is quite nice and effortless to experiment with the Malli library using a clojure REPL: just add &lt;code&gt;metosin/malli {:mvn/version "0.2.1"}&lt;/code&gt; (0.2.1 is the latest version as of writing this blog post - check for newer versions in &lt;a href="https://github.com/metosin/malli"&gt;malli github repo&lt;/a&gt; ) to your &lt;code&gt;deps.edn&lt;/code&gt; file and then require &lt;code&gt;malli&lt;/code&gt; and start experimenting with it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;malli&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;malli.core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;m/validate&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:closed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:ping&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:ping&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; =&amp;gt; true&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;m/validate&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:closed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:ping&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:wrong-key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; =&amp;gt; false&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;m/validate&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:closed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:ping&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:ping&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:extra-key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; =&amp;gt; false&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A common workflow in the Clojure land is to use a scratch REPL file to experiment with the data validation and once you are confident your validation works as it should you just move the validation code to the production code base.&lt;/p&gt;

&lt;h3&gt;
  
  
  Malli with Reitit
&lt;/h3&gt;

&lt;p&gt;As an exercise I implemented &lt;code&gt;malli&lt;/code&gt; validations to the REST api of the SimpleServer. In this chapter, I provide a few examples. The REST api can be found in the &lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/src/clj/simpleserver/webserver/server.clj"&gt;server.clj&lt;/a&gt; namespace.&lt;/p&gt;

&lt;p&gt;Let’s first use the &lt;code&gt;ping&lt;/code&gt; api as an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"/api"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:swagger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:tags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"api"&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;; For development purposes. Try curl http://localhost:6161/api/ping&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"/ping"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:summary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ping get"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="c1"&gt;; Don't allow any query parameters.&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="no"&gt;:parameters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:map&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="no"&gt;:responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Ping success"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="no"&gt;:handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;constantly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;make-response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:ok,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:reply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"pong"&lt;/span&gt;&lt;span class="p"&gt;}))}&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="no"&gt;:post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:summary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ping post"&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="no"&gt;:responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Ping success"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="c1"&gt;;; reitit adds mt/strip-extra-keys-transformer - probably changes in reitit 1.0,&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="c1"&gt;;; and therefore {:closed true} is not used with reitit &amp;lt; 1.0.&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="no"&gt;:parameters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:closed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:ping&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;]]}&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="no"&gt;:handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:parameters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
                                      &lt;/span&gt;&lt;span class="n"&gt;myreq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:ping&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
                                  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;myreq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:reply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"pong"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                                      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;make-response&lt;/span&gt;&lt;span class="p"&gt;))))}}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;ping&lt;/code&gt; GET I define that we don’t allow any query parameters, the query map is empty: &lt;code&gt;:parameters {:query [:map]}&lt;/code&gt;. For &lt;code&gt;ping&lt;/code&gt; POST we define the only allowed body parameter &lt;code&gt;:ping&lt;/code&gt;: &lt;code&gt;:parameters {:body [:map {:closed true} [:ping string?]]}&lt;/code&gt;. &lt;code&gt;malli&lt;/code&gt; is open by default (allows extra keys in maps), but you can configure your schema as closed using &lt;code&gt;{:closed true}&lt;/code&gt;. The comment block mentions that at the moment &lt;code&gt;reitit&lt;/code&gt; adds &lt;code&gt;mt/strip-extra-keys-transformer&lt;/code&gt; and therefore we don’t have any extra keys when we hit the validation - I left the &lt;code&gt;{:closed true}&lt;/code&gt; here just as an example.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/test/clj/simpleserver/webserver/server_test.clj"&gt;server_test.clj&lt;/a&gt; provides tests to validate that our &lt;code&gt;malli&lt;/code&gt; validation works. First the GET &lt;code&gt;ping&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;deftest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ping-get-test&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ENTER ping-get-test"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;testing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"GET: /api/ping"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ss-tc/-call-api&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ping"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:reply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"pong"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;})))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;deftest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;failed-ping-get-extra-query-params-test&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ENTER failed-ping-get-extra-query-params-test"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;testing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"GET: /api/ping"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ss-tc/-call-api&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ping?a=1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:coercion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"malli"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:humanized&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"disallowed key"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"request"&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="s"&gt;"query-params"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"reitit.coercion/request-coercion"&lt;/span&gt;&lt;span class="p"&gt;})))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When calling just GET &lt;code&gt;ping&lt;/code&gt; api we must succeed. When adding any query parameters (&lt;code&gt;"ping?a=1"&lt;/code&gt; in the example) we must fail. &lt;code&gt;malli&lt;/code&gt; also provides nice humanized error messages regarding the validations: &lt;code&gt;:humanized {:a ["disallowed key"]}&lt;/code&gt;. You get the humanized error messages by providing &lt;code&gt;:humanized&lt;/code&gt; in the &lt;code&gt;:error-keys&lt;/code&gt; set when you create the coercion to be used with reitit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-ring/ring-handler&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-ring/router&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="no"&gt;:data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:muuntaja&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mu-core/instance&lt;/span&gt;&lt;span class="w"&gt;
                                     &lt;/span&gt;&lt;span class="no"&gt;:coercion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reitit.coercion.malli/create&lt;/span&gt;&lt;span class="w"&gt;
                                                 &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="c1"&gt;;; set of keys to include in error messages&lt;/span&gt;&lt;span class="w"&gt;
                                                  &lt;/span&gt;&lt;span class="no"&gt;:error-keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:coercion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:humanized&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the POST &lt;code&gt;ping&lt;/code&gt; tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;deftest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ping-post-test&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ENTER ping-post-test"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;testing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POST: /api/ping"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ss-tc/-call-api&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ping"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:ping&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;})]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:reply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"pong"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;})))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;deftest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;failed-ping-post-missing-key-test&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ENTER failed-ping-post-missing-key-test"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;testing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POST: /api/ping"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ss-tc/-call-api&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ping"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:wrong-key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;})]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:coercion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"malli"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:humanized&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:ping&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"missing required key"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"request"&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="s"&gt;"body-params"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"reitit.coercion/request-coercion"&lt;/span&gt;&lt;span class="p"&gt;})))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The POST &lt;code&gt;ping&lt;/code&gt; must succeed if we provide the required &lt;code&gt;:ping&lt;/code&gt; key in the body map. If the required key is missing we get a humanized error: &lt;code&gt;:humanized {:ping ["missing required key"]}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s provide another example, POST &lt;code&gt;signin&lt;/code&gt;. The meaning of this API is to provide the user a method to sign-in to the web-store. The API requires the user to send his first-name, last-name, email, and password:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"/signin"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:summary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Sign-in to get an account"&lt;/span&gt;&lt;span class="w"&gt;
                       &lt;/span&gt;&lt;span class="no"&gt;:responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Sign-in success"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
                       &lt;/span&gt;&lt;span class="no"&gt;:parameters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:map&lt;/span&gt;&lt;span class="w"&gt;
                                           &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:first-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                                           &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:last-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                                           &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                                           &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;]]}&lt;/span&gt;&lt;span class="w"&gt;
                       &lt;/span&gt;&lt;span class="no"&gt;:handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                                  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:parameters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
                                        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;first-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                                    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-signin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;first-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)))}}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In real-world projects, if you have a big structured POST body, you should define the validation map as a &lt;code&gt;def&lt;/code&gt; making the routes part more readable. This way you can also compose validations from other validations. Let’s imagine instead of the previous flat representation we could have a more structured POST body, like &lt;code&gt;{:name {:first-name "mikko" :last-name "mikkonen"} :password "salainen" :email "mikko.mikkonen@foo.com"}&lt;/code&gt;. We could have defined the validation schema separately as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name-schema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:closed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:first-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:last-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sign-in-schema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name-schema&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; and then in the routes:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"/signin"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:summary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Sign-in to get an account"&lt;/span&gt;&lt;span class="w"&gt;
                       &lt;/span&gt;&lt;span class="no"&gt;:responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Sign-in success"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
                       &lt;/span&gt;&lt;span class="no"&gt;:parameters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sign-in-schema&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let’s experiment with our &lt;code&gt;name-schema&lt;/code&gt; and &lt;code&gt;sign-in-schema&lt;/code&gt; a bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;malli&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;malli.core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;malli.error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;me&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name-schema&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;m/explain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:first-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"mikko"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:last-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"mikkonen"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;me/humanize&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; =&amp;gt; nil (ok)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name-schema&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;m/explain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:first-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"mikko"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:middle-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"pekka"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:last-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"mikkonen"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;me/humanize&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; =&amp;gt; {:middle-name ["disallowed key"]}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sign-in-schema&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;m/explain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:first-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"mikko"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:last-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"mikkonen"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"salainen"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"mikko.mikkonen@foo.com"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;me/humanize&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; =&amp;gt; nil (ok)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sign-in-schema&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;m/explain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:first-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"mikko"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:last-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"mikkonen"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"salainen"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"mikko.mikkonen@foo.com"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;me/humanize&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; =&amp;gt; nil (also ok)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I.e. Our &lt;code&gt;name-schema&lt;/code&gt; is closed - no middle-name allowed. But we have left the &lt;code&gt;sign-in-schema&lt;/code&gt; open - therefore the user of the schema can add extra keys (as &lt;code&gt;:id&lt;/code&gt; in the example above). If you want to close the whole schema &lt;code&gt;malli.util&lt;/code&gt; provides a function for this: &lt;code&gt;closed-schema&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;malli&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;malli.core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;malli.error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;me&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;malli.util&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;clojure.data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;data/diff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;m/schema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sign-in-schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mu/closed-schema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sign-in-schema&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;; =&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;;[[:map &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;; [:name [:map {:closed true} [:first-name string?] [:last-name string?]]] &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;; [:email string?] &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;; [:password string?]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;; [:map&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;; {:closed true}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;; [:name [:map {:closed true} [:first-name string?] [:last-name string?]]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;; [:email string?]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;; [:password string?]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;; nil]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;I’m sure the &lt;code&gt;spec&lt;/code&gt; library is great for spec’ing the Clojure itself. But for providing a schema for REST interfaces I would use &lt;code&gt;malli&lt;/code&gt;. &lt;code&gt;spec&lt;/code&gt; uses macros for specifying schemas - &lt;code&gt;malli&lt;/code&gt; uses pure data: vectors and maps. In my opinion, this makes &lt;code&gt;malli&lt;/code&gt; nicer to work with schemas and also providing various technical advantages, e.g. you can manipulate &lt;code&gt;malli&lt;/code&gt; schemas using any standard library functions that operate on vectors and maps.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The writer is working at Metosin using Clojure in cloud projects. If you are interested in starting a Clojure project or getting Clojure training in Finland you can contact me by sending an email to my Metosin email address or contact me via LinkedIn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Kari Marttila&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kari Marttila’s Home Page in LinkedIn: &lt;a href="https://www.linkedin.com/in/karimarttila/"&gt;https://www.linkedin.com/in/karimarttila/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>clojure</category>
      <category>repl</category>
      <category>malli</category>
    </item>
    <item>
      <title>GCP Kubernetes Exercise</title>
      <dc:creator>Kari Marttila</dc:creator>
      <pubDate>Sat, 28 Nov 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/karimarttila/gcp-kubernetes-exercise-4h1k</link>
      <guid>https://dev.to/karimarttila/gcp-kubernetes-exercise-4h1k</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fwww.karimarttila.fi%2Fimg%2F2020-11-28-gcp-kubernetes-exercise_img_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fwww.karimarttila.fi%2Fimg%2F2020-11-28-gcp-kubernetes-exercise_img_1.png" alt="The workloads in the GKE cluster"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The workloads in the GKE cluster.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;I have earlier used Terraform to create Kubernetes in AWS &lt;a href="https://aws.amazon.com/eks/" rel="noopener noreferrer"&gt;EKS&lt;/a&gt; (Amazon Elastic Kubernetes Service) and Azure &lt;a href="https://azure.microsoft.com/en-us/services/kubernetes-service/" rel="noopener noreferrer"&gt;AKS&lt;/a&gt; (Azure Kubernetes Service) and therefore I wanted to create Kubernetes also in the &lt;a href="https://cloud.google.com/gcp/" rel="noopener noreferrer"&gt;GCP&lt;/a&gt; (Google Cloud Platform) to have some perspective on how different the Kubernetes infrastructure is to create in these three major clouds.&lt;/p&gt;

&lt;p&gt;My earlier AWS EKS and Azure AKS blog posts are here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.karimarttila.fi/azure/2019/01/07/creating-azure-kubernetes-service-aks-the-right-way.html" rel="noopener noreferrer"&gt;Creating Azure Kubernetes Service (AKS) the Right Way&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.karimarttila.fi/aws/2019/01/18/creating-aws-elastic-container-service-for-kubernetes-eks-the-right-way.html" rel="noopener noreferrer"&gt;Creating AWS Elastic Container Service for Kubernetes (EKS) the Right Way&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This GCP GKE Kubernetes exercise can be found in my &lt;a href="https://github.com/karimarttila/gcp" rel="noopener noreferrer"&gt;gcp&lt;/a&gt; repo in directory &lt;a href="https://github.com/karimarttila/gcp/tree/kube-exercise/simpleserver-kube" rel="noopener noreferrer"&gt;simpleserver-kube&lt;/a&gt;. I might, later on, continue with this exercise - creating a &lt;a href="https://helm.sh/" rel="noopener noreferrer"&gt;Helm&lt;/a&gt; chart for the Clojure simple server to be deployed to this GKE cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initialization Scripts
&lt;/h3&gt;

&lt;p&gt;You can create the solution in many ways. You could have a &lt;a href="https://cloud.google.com/storage/docs/projects" rel="noopener noreferrer"&gt;GCP Project&lt;/a&gt; made for you and you have to use that specific project. I didn’t have those kinds of restrictions when making this exercise. In this exercise, I used Admin project / Infra project pattern. I.e. I create an Admin project just for hosting the &lt;a href="https://cloud.google.com/iam/docs/service-accounts" rel="noopener noreferrer"&gt;GCP Service Account&lt;/a&gt; - the Service Account is used by Terraform to create the actual Infra project and all resources belonging to that infra project. Let’s walk through these steps (one-time initialization - later on, you just use Terraform to develop the solution).&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://github.com/karimarttila/gcp/tree/kube-exercise/simpleserver-kube/init" rel="noopener noreferrer"&gt;init&lt;/a&gt; directory you can find a few scripts that I used to automate the admin project infrastructure. I gathered all relevant information I needed into the &lt;a href="https://github.com/karimarttila/gcp/blob/kube-exercise/simpleserver-kube/init/env-vars-template.sh" rel="noopener noreferrer"&gt;env-vars-template.sh&lt;/a&gt; file - one should copy-paste this template e.g. to ~/.gcp/my-kube-env-vars.sh and provide the values for the environment variables. Not all environment variables are needed, I created a bunch of them, some are for administrational purposes and used to label resources (optional), but some are needed by GCP (billing information) and to create the resources (admin / infra project id…).&lt;/p&gt;

&lt;p&gt;Then you are ready to create the Admin project and related resources: &lt;a href="https://github.com/karimarttila/gcp/blob/kube-exercise/simpleserver-kube/init/create-admin-project.sh" rel="noopener noreferrer"&gt;create-admin-project.sh&lt;/a&gt;. This script just uses the environment variables and creates the admin project, then sets certain configuration values and creates a &lt;a href="https://cloud.google.com/sdk/gcloud" rel="noopener noreferrer"&gt;gcloud&lt;/a&gt; configuration for the admin project. Finally, we link the billing account to this admin project and enable &lt;code&gt;container.googleapis.com&lt;/code&gt; so that the Service Account that belongs to this admin project and is used by Terraform can, later on, create GKE cluster.&lt;/p&gt;

&lt;p&gt;We are going to store Terraform state in the GCP &lt;a href="https://cloud.google.com/storage/docs/json_api/v1/buckets" rel="noopener noreferrer"&gt;Cloud Storage Bucket&lt;/a&gt; therefore we need to create it: &lt;a href="https://github.com/karimarttila/gcp/blob/kube-exercise/simpleserver-kube/init/create-admin-bucket.sh" rel="noopener noreferrer"&gt;create-admin-bucket.sh&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, we are ready to create the last admin project resource: the Service Account that is used by Terraform to create the resources in the infra project side: &lt;a href="https://github.com/karimarttila/gcp/blob/kube-exercise/simpleserver-kube/init/create-service-account.sh" rel="noopener noreferrer"&gt;create-service-account.sh&lt;/a&gt;. The script also binds certain roles to that Service Account - e.g. the role needed to create the infra project.&lt;/p&gt;

&lt;p&gt;The last thing to create is the infra project gcloud configurations. Since we already know the infra project id we are going to use let’s create the infra configuration right now: &lt;a href="https://github.com/karimarttila/gcp/blob/kube-exercise/simpleserver-kube/init/create-infra-configuration.sh" rel="noopener noreferrer"&gt;create-infra-configuration.sh&lt;/a&gt;. This way the gcloud infra configuration is ready when we start creating the infra resources - we can use this gcloud configuration to examine the resources with the gcloud cli.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terraform Solution
&lt;/h3&gt;

&lt;p&gt;I have often used a kind of “Mother Module” Terraform pattern, e.g. &lt;a href="https://github.com/karimarttila/aws/blob/master/simple-server-eks/terraform/modules/env-def/env-def.tf" rel="noopener noreferrer"&gt;env-def.tf&lt;/a&gt; in one of my previous exercises. But this autumn I and my colleague Kimmo Koskinen created an AWS Terraform solution to be used internally in Metosin cloud training and also in our AWS projects and - my colleague Kimmo Koskinen suggested an “Independent Terraform States by Module” solution which is quite nice. It provides a nice way to create independent Terraform states per module and you can create and develop the modules independently as the name suggests. We wrote a blog post regarding this work: &lt;a href="https://www.metosin.fi/blog/terraform-ecs-example/" rel="noopener noreferrer"&gt;Terraform ECS Example&lt;/a&gt;. So, I used this “Independent Terraform States by Module” solution in this GCP GKE exercise. There are basically three independent Terraform modules in the &lt;a href="https://github.com/karimarttila/gcp/tree/kube-exercise/simpleserver-kube/terraform" rel="noopener noreferrer"&gt;terraform&lt;/a&gt; directory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/karimarttila/gcp/tree/kube-exercise/simpleserver-kube/terraform/project" rel="noopener noreferrer"&gt;project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/karimarttila/gcp/tree/kube-exercise/simpleserver-kube/terraform/vpc" rel="noopener noreferrer"&gt;vpc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/karimarttila/gcp/tree/kube-exercise/simpleserver-kube/terraform/kube" rel="noopener noreferrer"&gt;kube&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All modules comprise a &lt;code&gt;setup.tf&lt;/code&gt; file that includes the Terraform google provider and the state configuration. All modules comprise also &lt;code&gt;main.tf&lt;/code&gt;, &lt;code&gt;variables.tf&lt;/code&gt;, and &lt;code&gt;outputs.tf&lt;/code&gt; files - respectively giving configurations for the main resources, variables used, and outputs.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;project&lt;/code&gt; and &lt;code&gt;vpc&lt;/code&gt; modules just create the infra project and the vpc used in this project and are more or less trivialities. Let’s spend some time with the &lt;code&gt;kube&lt;/code&gt; module instead.&lt;/p&gt;

&lt;p&gt;The GKE Terraform configuration is ridiculously small, just 60 lines. First, we create the cluster itself and then the nodes used by the cluster. The simplicity and easiness of the GKE Terraform solution was a pleasant surprise. And more surprises ahead: it took just some 60 seconds for Terraform to create the GKE cluster. I remember that in our previous project creating AWS EKS using Pulumi took quite a while.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting and Testing the GKE Cluster
&lt;/h3&gt;

&lt;p&gt;Use gcloud to get the credentials for the new GKE cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; gcloud container clusters get-credentials YOUR-CLUSTER-NAME &lt;span class="nt"&gt;--region&lt;/span&gt; YOUR-REGION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can use &lt;a href="https://kubernetes.io/docs/reference/kubectl/kubectl/" rel="noopener noreferrer"&gt;kubectl&lt;/a&gt; cli tool to examine the GKE cluster and its resources. Another nice tool is &lt;a href="https://k8slens.dev/" rel="noopener noreferrer"&gt;Lens&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Just to make sure the cluster works properly I deployed some dummy service to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create deployment hello-server &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gcr.io/google-samples/hello-app:1.0
kubectl get pods &lt;span class="nt"&gt;--all-namespaces&lt;/span&gt;
kubectl expose deployment hello-server &lt;span class="nt"&gt;--type&lt;/span&gt; LoadBalancer &lt;span class="nt"&gt;--port&lt;/span&gt; 80 &lt;span class="nt"&gt;--target-port&lt;/span&gt; 8080
kubectl get service hello-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll get the external IP for the service and you can curl it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;λ&amp;gt; curl http://EXTERNAL-IP
Hello, world!
Version: 1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resource Naming
&lt;/h3&gt;

&lt;p&gt;I like to name my cloud resources so that all resources have a prefix providing information regarding the project and the environment. We used this same strategy with my colleague in the project I previously mentioned. I used this same strategy in this exercise. Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;workspace_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;terraform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace&lt;/span&gt;
  &lt;span class="nx"&gt;module_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"kube"&lt;/span&gt;
  &lt;span class="nx"&gt;res_prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.PREFIX}-${local.workspace_name}"&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_container_cluster"&lt;/span&gt; &lt;span class="s2"&gt;"kube-cluster"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${local.res_prefix}-${local.module_name}-cluster"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, if the prefix is the project name (e.g. &lt;code&gt;projx&lt;/code&gt;) and the Terraform workspace is e.g. &lt;code&gt;dev&lt;/code&gt; all resources have a resource prefix &lt;code&gt;projx-dev&lt;/code&gt;. E.g. the gke cluster name will be: &lt;code&gt;projx-dev-kube-cluster&lt;/code&gt;. You can have many environments in the same GCP account using this pattern, e.g. &lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;qa&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt; etc. - all environments have a dedicated Terraform state. Just to make it explicit - you should always keep your production environment in a dedicated production account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Patterns for Creating Environments
&lt;/h3&gt;

&lt;p&gt;Since it is so easy and quick to create new environments using GKE I wouldn’t simulate environments using Kubernetes namespaces in the same GKE cluster. You can do this but interacting with the other resources in the same environment would be complex (inside the Kubernetes cluster you can have a dynamic number of virtual environments but outside the Kubernetes cluster you typically have one environment, the one the cluster itself belongs to). Instead, I would just create exact copies of the environments (by parameterizing the instance sizes, of course). Read more about this pattern in my earlier blog post &lt;a href="http://www.karimarttila.fi/iac/2020/04/10/cloud-infrastructure-golden-rules.html" rel="noopener noreferrer"&gt;Cloud Infrastructure Golden Rules&lt;/a&gt; .&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;It was a real positive surprise how easy it is to set up a Kubernetes cluster using GCP GKE service and Terraform. Considering my experiences using Kubernetes with AWS and GCP I would recommend using the simplest solution for running containers in each cloud: with AWS, use ECS; with GCP, use GKE.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The writer is working at Metosin using Clojure in cloud projects. If you are interested to start a Clojure project in Finland or you are interested in getting Clojure training in Finland you can contact me by sending an email to my Metosin email address or contact me via LinkedIn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Kari Marttila&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kari Marttila’s Home Page in LinkedIn: &lt;a href="https://www.linkedin.com/in/karimarttila/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/karimarttila/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gcp</category>
      <category>cloud</category>
      <category>iac</category>
    </item>
    <item>
      <title>Clojure Datomic Exercise</title>
      <dc:creator>Kari Marttila</dc:creator>
      <pubDate>Sat, 14 Nov 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/karimarttila/clojure-datomic-exercise-5h1f</link>
      <guid>https://dev.to/karimarttila/clojure-datomic-exercise-5h1f</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uWiqGGVQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-11-14-clojure-datomic-exercise_img_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uWiqGGVQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-11-14-clojure-datomic-exercise_img_1.png" alt="IntelliJ IDEA and Cursive"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Clojure Datomic Exercise in IntelliJ IDEA / Cursive IDE.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;In my previous &lt;a href="https://www.karimarttila.fi/clojure/2020/09/07/clojure-integrant-exercise.html"&gt;Integrant exercise&lt;/a&gt;, I had converted my earlier SimpleServer exercise to use the &lt;a href="https://github.com/weavejester/integrant"&gt;Integrant&lt;/a&gt; state management library. In that Integrant exercise, there were three datastores: in-memory datastore that read the initial data from CSV files, &lt;a href="https://aws.amazon.com/dynamodb/"&gt;AWS DynamoDB&lt;/a&gt; datastore, and &lt;a href="https://www.postgresql.org/"&gt;PostgreSQL&lt;/a&gt; datastore. I implemented the domain layer using &lt;a href="https://clojure.org/reference/protocols"&gt;Clojure Protocols&lt;/a&gt; so that in the application it was easy to switch the datastore by changing the value in one Integrant configuration (&lt;code&gt;:backend/active-db&lt;/code&gt;) - and reset the application state by using &lt;a href="https://github.com/weavejester/integrant-repl"&gt;Integrant reset&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My next exercise was to implement a frontend to that application using &lt;a href="https://github.com/day8/re-frame"&gt;re-frame&lt;/a&gt;, read more about that exercise in my blog article &lt;a href="https://www.karimarttila.fi/clojure/2020/10/15/clojure-re-frame-exercise.html"&gt;Clojure Re-Frame Exercise&lt;/a&gt;. Since this new Datomic exercise is basically the same exercise as the previous re-frame exercise except the new Datomic datastore added, I created this exercise into the same re-frame application directory.&lt;/p&gt;

&lt;p&gt;So, this Datomic exercise can be found in my &lt;a href="https://github.com/karimarttila/clojure"&gt;Clojure&lt;/a&gt; repo in directory &lt;a href="https://github.com/karimarttila/clojure/tree/master/webstore-demo/re-frame-demo"&gt;re-frame&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Datomic?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.datomic.com/"&gt;Datomic&lt;/a&gt; is an immutable transactional hosted database that uses &lt;a href="https://en.wikipedia.org/wiki/Datalog"&gt;Datalog&lt;/a&gt; language for queries. Being hosted means that Datomic uses another data store service, e.g. DynamoDB, for persistence.&lt;/p&gt;

&lt;p&gt;If you are interested to learn more about Datomic I list below some Datomic resources I used myself while implementing this exercise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.datomic.com/"&gt;Datomic Home Page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.datomic.com/cloud/index.html"&gt;Datomic Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.datomic.com/cloud/dev-local.html"&gt;Datomic Dev-Local&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cognitect.com/dev-tools/index.html"&gt;Cognitect dev-tools download&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.datomic.com/on-prem/day-of-datomic.html"&gt;Day of Datomic 2016 Video Series&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Datomic/day-of-datomic"&gt;Day of Datomic 2016 Github Repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.learndatalogtoday.org/"&gt;Learn Datalog Today&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grishaev.me/en/pg-to-datomic/"&gt;Migration from Postgres to Datomic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.datomic.com/on-prem/dev-setup.html"&gt;Local Dev Setup&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I strongly recommend to read &lt;a href="http://www.learndatalogtoday.org/"&gt;Learn Datalog Today&lt;/a&gt; and do all exercises - a great resource to learn the &lt;a href="https://docs.datomic.com/on-prem/query.html"&gt;Datalog&lt;/a&gt; language. If you are interested in watching the Day of Datomic videos there are newer videos from 2018 in Youtube. Stuart Halloway is an excellent presenter and it was a joy to watch those video presentations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choosing Datomic Version
&lt;/h3&gt;

&lt;p&gt;Choosing the Datomic version for development was a bit confusing: should I choose Datomic dev-local, Datomic Free, or Datomic Starter? Finally, I decided to use &lt;a href="https://www.datomic.com/get-datomic.html"&gt;Datomic Starter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I used the following instructions to set up the local Datomic Starter instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.datomic.com/on-prem/dev-setup.html"&gt;Local Dev Setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.datomic.com/on-prem/get-datomic.html"&gt;Get Datomic&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I must say that choosing the right Datomic version was a bit confusing - not to talk about the documentation. There really should be one dedicated “start here” page which could provide easy to follow instructions for a complete Datomic newcomer.&lt;/p&gt;

&lt;p&gt;Once you have unzipped the Datomic server installation package, configure the &lt;code&gt;dev-transactor-template.properties&lt;/code&gt; as instructed in the Datomic documentation and then run the &lt;code&gt;bin/maven-install&lt;/code&gt; to install the library jar. Add the dependency to your project’s &lt;code&gt;deps.edn&lt;/code&gt; file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Datomic Tooling
&lt;/h3&gt;

&lt;p&gt;I created some &lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/Justfile"&gt;Just recipes&lt;/a&gt; for Datomic development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start local Datomic database.&lt;/span&gt;
@datomic:
    &lt;span class="nb"&gt;cd &lt;/span&gt;datomic &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./start-datomic.sh

&lt;span class="c"&gt;# Reset Datomic SimpleServer databases.&lt;/span&gt;
@datomic-ss-reset:
    &lt;span class="nb"&gt;cd &lt;/span&gt;datomic &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./reset-simpleserver-db.sh

&lt;span class="c"&gt;# Start Datomic Peer Server. NOTE: NOT NEEDED IN THIS EXERCISE!&lt;/span&gt;
@datomic-peer-server:
    &lt;span class="nb"&gt;cd &lt;/span&gt;datomic &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./start-peer-server.sh

&lt;span class="c"&gt;# Start Datomic Console.&lt;/span&gt;
@datomic-console:
    &lt;span class="nb"&gt;cd &lt;/span&gt;datomic &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./start-console.sh

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Short descriptions of the Just recipes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;datomic: Starts the Datomic database (transactor etc.).&lt;/li&gt;
&lt;li&gt;datomic-ss-reset: Deletes and creates simpleserver and simpleserver_test databases (for clean development start).&lt;/li&gt;
&lt;li&gt;datomic-peer-server: Starts the Datomic Peer Server. Not needed in this exercise, I just tested that it works.&lt;/li&gt;
&lt;li&gt;datomic-console: Starts the Datomic Console server, open Chrome: &lt;code&gt;http://localhost:8080/browse&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;datomic-ss-reset&lt;/code&gt; is a bit of a hack, calls &lt;code&gt;reset-simpleserver-db.sh&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;THIS_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;TRANSACTOR_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/mnt/ssd2/local/datomic-pro-1.0.6202
&lt;span class="nb"&gt;pushd&lt;/span&gt; &lt;span class="nv"&gt;$THIS_DIR&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$TRANSACTOR_DIR&lt;/span&gt;
&lt;span class="nb"&gt;pwd
sleep &lt;/span&gt;2
bin/repl &amp;lt; &lt;span class="nv"&gt;$THIS_DIR&lt;/span&gt;/reset-simpleserver-db.clj
&lt;span class="nb"&gt;popd&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, it starts the &lt;code&gt;repl&lt;/code&gt; which is provided by the Datomic server. For some reason, there is no &lt;code&gt;deps.edn&lt;/code&gt; for this repl but a cryptic shell script which populates the jar files, the reason I start it like this. I pipe the required function calls to this repl, see &lt;code&gt;reset-simpleserver-db.clj&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;datomic.api&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;" **********************************"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Starting to reset Simpleserver databases..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db-uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"datomic:dev://localhost:4334/simpleserver"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test-db-uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"datomic:dev://localhost:4334/simpleserver_test"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Listing databases before reset..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/get-database-names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"datomic:dev://localhost:4334/*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Deleting databases..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/delete-database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db-uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/delete-database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test-db-uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Listing databases after deletes..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/get-database-names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"datomic:dev://localhost:4334/*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Creating databases..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/create-database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db-uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/create-database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test-db-uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Listing databases after creations..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/get-database-names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"datomic:dev://localhost:4334/*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exercise-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/a/prs/github/clojure/webstore-demo/re-frame-demo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ss-schema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;read-string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;slurp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exercise-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/datomic/simpleserver-schema.edn"&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ss-conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/connect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db-uri&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test-ss-conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/connect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test-db-uri&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/transact&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ss-conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ss-schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/transact&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test-ss-conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ss-schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product-groups&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;load-product-groups&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product-group-datoms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-product-group-datoms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product-groups&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/transact&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ss-conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product-group-datoms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An easy way to create the development and test databases and transact the schema to the databases, and then transact the development data to the database.&lt;/p&gt;

&lt;p&gt;Now that we have some real data in the Datomic database let’s look at the data using the Datomic Console we started earlier with the Just recipe (open Chrome and navigate to &lt;a href="http://localhost:8080/browse):"&gt;http://localhost:8080/browse):&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---pDIzO6B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-11-14-clojure-datomic-exercise_img_2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---pDIzO6B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-11-14-clojure-datomic-exercise_img_2.png" alt="Datomic Console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Datomic Console.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I ran the same query I can also run quite easily with the Clojure REPL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?a_or_d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?year&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="no"&gt;:in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?pg-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?y&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="no"&gt;:where&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:domain.product/title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?title&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:domain.product/year&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?year&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:domain.product/a_or_d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?a_or_d&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:domain.product/pg-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?pg-id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="nb"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?year&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?y&lt;/span&gt;&lt;span class="p"&gt;)]]&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ss-conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I.e. Find all products that belong to the product group &lt;code&gt;2&lt;/code&gt; and are created after year &lt;code&gt;2000&lt;/code&gt; (including): print the title, author/director and year. In REPL you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="s"&gt;"A History of Violence"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Cronenberg, David"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2005&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Mulholland Dr."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Lynch, David"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2001&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Paha maa"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Aku Louhimies"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2005&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Avatar"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Cameron, James"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2009&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Hyvä poika"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Zaida Bergroth"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2011&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I didn’t use the Datomic Console at all during the exercise. It was a lot easier to develop and test the queries interactively using the Clojure REPL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Experimenting with the Datomic Sample
&lt;/h3&gt;

&lt;p&gt;There are quite a lot of samples with the Datomic Starter installation package. Unfortunately, there was no standard &lt;code&gt;deps.edn&lt;/code&gt; file with the samples. Therefore I added &lt;a href="https://github.com/nrepl/nrepl"&gt;nrepl&lt;/a&gt; to the run script to be able to start a nrepl server and then connect to that server using IntelliJ IDEA / Cursive to make it easier to experiment e.g. with the Seattle sample that was provided with the installation package.&lt;/p&gt;

&lt;p&gt;I also threw the Seattle sample data to the Datomic dev server instead of the in-memory server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up the Connection to the Datomic Database
&lt;/h3&gt;

&lt;p&gt;The connection to the Datomic database is created in the Integrant state management of the application:&lt;/p&gt;

&lt;p&gt;Integrant configuration in &lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/resources/config.edn"&gt;config.edn&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:backend/datomic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:active-db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;ig/ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:backend/active-db&lt;/span&gt;&lt;span class="w"&gt;
                   &lt;/span&gt;&lt;span class="no"&gt;:uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"datomic:dev://localhost:4334/simpleserver"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the state set up in &lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/src/clj/simpleserver/core.clj"&gt;core.clj&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;simpleserver.core&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:require&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;datomic.api&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defmethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ig/init-key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:backend/datomic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;active-db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;]}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ENTER ig/init-key :backend/datomic"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;active-db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:datomic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/connect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)}))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using the Datomic Api
&lt;/h3&gt;

&lt;p&gt;Using the Datomic api was really easy and experimenting with the api using the Clojure REPL was a real joy. Some examples.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transact the schema&lt;/strong&gt; to the Datomic database (&lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/datomic/reset-simpleserver-db.clj"&gt;reset-simpleserver-db.clj&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db-uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"datomic:dev://localhost:4334/simpleserver"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/create-database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db-uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exercise-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/a/prs/github/clojure/webstore-demo/re-frame-demo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ss-schema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;read-string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;slurp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exercise-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/datomic/simpleserver-schema.edn"&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ss-conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/connect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db-uri&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/transact&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ss-conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ss-schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema consists of the same datoms that you use also in ordinary queries (&lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/datomic/simpleserver-schema.edn"&gt;simpleserver-schema.edn&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="c1"&gt;; DOMAIN&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="c1"&gt;; Product group&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:db/ident&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:domain.product-group/id&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;:db/valueType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:db.type/long&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;:db/cardinality&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:db.cardinality/one&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;:db/unique&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:db.unique/identity&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;:db/doc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"The id of the product group"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

 &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:db/ident&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:domain.product-group/name&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;:db/valueType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:db.type/string&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;:db/cardinality&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:db.cardinality/one&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;:db/doc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"The name of the product group"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

 &lt;/span&gt;&lt;span class="c1"&gt;; Product&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:db/ident&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:domain.product/id&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;:db/valueType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:db.type/long&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;:db/cardinality&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:db.cardinality/one&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;:db/unique&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:db.unique/identity&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;:db/doc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"The id of the product"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Transact data&lt;/strong&gt; to the Datomic database (&lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/datomic/simpleserver-schema.edn"&gt;simpleserver-schema.edn&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get-product-group-datoms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;product-groups&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mapv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;product-group&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product-group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product-group&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:domain.product-group/id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="no"&gt;:domain.product-group/name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;product-groups&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product-groups&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;load-product-groups&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product-group-datoms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-product-group-datoms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product-groups&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/transact&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ss-conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product-group-datoms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Query datoms&lt;/strong&gt; from the Datomic database (&lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/src/clj/simpleserver/service/user/user_datomic.clj"&gt;user-datomic.clj&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;credentials-ok?&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ENTER credentials-ok?"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?hashed-password&lt;/span&gt;&lt;span class="w"&gt;
                       &lt;/span&gt;&lt;span class="no"&gt;:in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?hashed-password&lt;/span&gt;&lt;span class="w"&gt;
                       &lt;/span&gt;&lt;span class="no"&gt;:where&lt;/span&gt;&lt;span class="w"&gt;
                       &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:user.user/email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?email&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                       &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;?e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:user.user/hashed-password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?hashed-password&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)))]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;))]})&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Development Flow
&lt;/h3&gt;

&lt;p&gt;The development flow using the Clojure REPL and Integrant was really nice. Usually, I experimented the queries in my scratch file (see more about this technique in my blog post &lt;a href="https://www.karimarttila.fi/clojure/2020/10/26/clojure-power-tools-part-1.html"&gt;Clojure Power Tools Part 1&lt;/a&gt;). Once I was confident the query worked I moved the code snippet from the scratch file to the production source file and reset Integrant state (with IntelliJ IDEA hotkey, of course), and ran the unit tests to see that the code worked also as part of the application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;It was really fun to do this exercise. The Day of Datomic video presentations were really good, also other material regarding Datomic and the Datalog query language. The exercise itself was quite effortless to implement using Datomic.&lt;/p&gt;

&lt;p&gt;I would definitely use Datomic with Clojure in real projects, too. The idea of an immutable database is a real nice fit for many use cases.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The writer is working at Metosin using Clojure in cloud projects. If you are interested to start a Clojure project in Finland or you are interested to get Clojure training in Finland you can contact me by sending email to my Metosin email address or contact me via LinkedIn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Kari Marttila&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kari Marttila’s Home Page in LinkedIn: &lt;a href="https://www.linkedin.com/in/karimarttila/"&gt;https://www.linkedin.com/in/karimarttila/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>clojure</category>
      <category>datomic</category>
      <category>repl</category>
    </item>
    <item>
      <title>Clojure Power Tools Part 2</title>
      <dc:creator>Kari Marttila</dc:creator>
      <pubDate>Thu, 29 Oct 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/karimarttila/clojure-power-tools-part-2-365j</link>
      <guid>https://dev.to/karimarttila/clojure-power-tools-part-2-365j</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OkBnAb57--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-29-clojure-power-tools-part-2_img_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OkBnAb57--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-29-clojure-power-tools-part-2_img_1.png" alt="IntelliJ IDEA and Cursive"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;This is the second part of my Clojure Power Tools series (I’m a bit interested myself how many blog posts I will write to this series). If you haven’t read the first part I recommend you to read it first: &lt;a href="https://www.karimarttila.fi/clojure/2020/10/26/clojure-power-tools-part-1.html"&gt;Clojure Power Tools Part 1&lt;/a&gt;. In this second blog article, I list a couple of new power tool tricks for debugging. I first introduce a poor man’s debug repl, and then the real debug repl.&lt;/p&gt;

&lt;h3&gt;
  
  
  Poor Man’s Debug Repl
&lt;/h3&gt;

&lt;p&gt;I was refactoring some Clojure tests for the second version of a complicated application. There were quite a few structural changes in the application logic, and the domain was rather complicated comprising a lot of various recursive data structures. In some tests, I was quite puzzled about what kind of state there was in several places of the application. The tests were also rather complicated and I wanted a simple way to record certain bindings in several places of the application when I was running a certain area of the test - and only that area. I quickly implemented a poor man’s debug recorder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;recorder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;atom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;atom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;atom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;next-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;swap!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;assoc-value-with-id!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;next-id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;new-k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;keyword&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;swap!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;assoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new-k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add-value-if-recording!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;assoc-value-with-id!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;start!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reset!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reset!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reset!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stop!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reset!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea is to provide a way to add stuff to the value map with unique keys - but only when I’m recording.&lt;/p&gt;

&lt;p&gt;(I’m using my own exercises as an example here, the data here is ridiculously simple - no need for a recorder here, but I guess you get my point if you change the data to several hundreds of lines of recursive maps and vectors and complex business logic…)&lt;/p&gt;

&lt;p&gt;So, let’s add the start/stop recording to the test we are interested:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;deftest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product-groups-test&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ENTER product-groups-test"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;testing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"GET: /api/product-groups"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re/start!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;login-ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ss-tc/-call-api&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"login"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"test-kari.karttinen@foo.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Kari"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Got login-ret: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;login-ret&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;json-web-token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;login-ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:json-web-token&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-create-basic-authentication&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;json-web-token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;get-ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ss-tc/-call-api&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/product-groups"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get-ret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get-ret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;right-body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ok"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:product-groups&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Test-Books"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Test-Movies"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re/stop!&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;nil?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;json-web-token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;right-body&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the &lt;code&gt;(re/start!)&lt;/code&gt; and &lt;code&gt;(re/stop!)&lt;/code&gt; commands in the test - we are recording only during that time.&lt;/p&gt;

&lt;p&gt;Then I can add stuff to my recorder where-ever I want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;-valid-token?&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"Parses the token from the http authorization header and asks session ns to validate the token."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ENTER -valid-token?"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;basic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"authorization"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re/add-value-if-recording!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:valid-token-basic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;basic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;basic-str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;basic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;last&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;re-find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="s"&gt;"^Basic (.*)$"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;basic&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I.e. &lt;code&gt;(re/add-value-if-recording! :valid-token-basic basic)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Run the tests.&lt;/p&gt;

&lt;p&gt;After the tests I can examine the recorder in my scratch file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;recorder&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;recorder/value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;recorder/value&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;portal.api&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;portal-api&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;portal.api/clear&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;portal.api/open&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;portal.api/tap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tap&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;recorder/value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also use the excellent &lt;a href="https://github.com/djblue/portal"&gt;portal&lt;/a&gt; visualization tool to examine your complicated data you recorded (well, in this example, not so complicated):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GxSMOEpD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-29-clojure-power-tools-part-2_img_2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GxSMOEpD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-29-clojure-power-tools-part-2_img_2.png" alt="Panel"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Real Debug-Repl
&lt;/h3&gt;

&lt;p&gt;The example above was a simple trick but a real Clojurian &lt;em&gt;stops the world&lt;/em&gt; if he is interested to see what is happening in a particular moment of time instead of just recording it. We are going to use Gary Fredericks’s &lt;a href="https://github.com/gfredericks/debug-repl"&gt;debug-repl&lt;/a&gt; library for it. You need the dependency - I have these tool dependencies in my &lt;code&gt;~/.clojure/deps.edn&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:aliases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="no"&gt;:kari&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:extra-paths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"scratch"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="no"&gt;:extra-deps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hashp/hashp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0.1.1"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="n"&gt;com.gfredericks/debug-repl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0.0.11"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="n"&gt;djblue/portal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0.6.1"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we need to be able to start the backend REPL with this debug repl middleware, I have this in my &lt;a href="https://github.com/casey/just"&gt;Justfile&lt;/a&gt; as a Just recipe which I use to start my backend repl with various options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Start backend repl with my toolbox and with debug-repl capability.
@backend-debug-kari:
    clj -M:dev:test:common:backend:kari -m nrepl.cmdline -m com.gfredericks.debug-repl/wrap-debug-repl -i -C

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can add a debug-repl breakpoint in your code, run your code, stop the world and examine the snapshot context of your world just before the breakpoint. Let’s see this in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XCA2nImx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-29-clojure-power-tools-part-2_img_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XCA2nImx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-29-clojure-power-tools-part-2_img_3.png" alt="debug-repl"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have added &lt;code&gt;_ (break! "Yihaa!")&lt;/code&gt; breakpoint in one of the tests. Then I run the test and the world stops at the breakpoint (&lt;code&gt;Hijacking repl for breakpoint: Yihaa!&lt;/code&gt; output in the REPL output window). Then I have moved the cursor in various let-bindings: &lt;code&gt;json-web-token&lt;/code&gt;, &lt;code&gt;params&lt;/code&gt; and &lt;code&gt;get-ret&lt;/code&gt; and evaluated the forms: you can see the evaluated values in the REPL output window on the right. If I try to evaluate &lt;code&gt;status&lt;/code&gt; I get an error “Unable to resolve symbol: status in this context” - of course, because it’s outside the context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;There are a lot of other Clojure tricks and tools - maybe I’ll write “Clojure Power Tools Part 3” blog post in the future.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The writer is working at Metosin using Clojure in cloud projects. If you are interested to start a Clojure project in Finland or you are interested to get Clojure training in Finland you can contact me by sending email to my Metosin email address or contact me via LinkedIn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Kari Marttila&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kari Marttila’s Home Page in LinkedIn: &lt;a href="https://www.linkedin.com/in/karimarttila/"&gt;https://www.linkedin.com/in/karimarttila/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>clojure</category>
      <category>repl</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Clojure Power Tools Part 1</title>
      <dc:creator>Kari Marttila</dc:creator>
      <pubDate>Mon, 26 Oct 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/karimarttila/clojure-power-tools-part-1-6i1</link>
      <guid>https://dev.to/karimarttila/clojure-power-tools-part-1-6i1</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VflfQovd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-26-clojure-power-tools-part-1_img_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VflfQovd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-26-clojure-power-tools-part-1_img_1.png" alt="IntelliJ IDEA and Cursive"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;I have been working at &lt;a href="https://www.metosin.fi"&gt;Metosin&lt;/a&gt; for some months now and I’m learning new Clojure tricks almost every day. I list here some of my favorite Clojure tricks that I have learned either from various Clojure presentations or at Metosin. I know that for a newcomer Clojure might be a bit weird language and newcomers usually don’t know simple productivity tricks. Hopefully, this presentation makes it a bit easier for newcomers to start being productive with Clojure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start Doing It
&lt;/h3&gt;

&lt;p&gt;If you are a complete newcomer in the Clojure land I have one recommendation: Just start doing it. Choose an editor, install the required tools, and start learning. The most important thing is that you start learning with a Clojure REPL. And with an editor that has good integration with the Clojure REPL. There are several options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.gnu.org/software/emacs/"&gt;Emacs&lt;/a&gt; with &lt;a href="https://github.com/clojure-emacs/cider"&gt;Cider&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.jetbrains.com/idea/"&gt;IntelliJ IDEA&lt;/a&gt; with &lt;a href="https://cursive-ide.com/"&gt;Cursive&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.visualstudio.com/"&gt;Visual Studio Code&lt;/a&gt; with &lt;a href="https://marketplace.visualstudio.com/items?itemName=betterthantomorrow.calva"&gt;Calva&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.vim.org/"&gt;Vim&lt;/a&gt; with &lt;a href="https://github.com/tpope/vim-fireplace"&gt;fireplace.vim&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://atom.io/"&gt;Atom&lt;/a&gt; with &lt;a href="https://atom.io/packages/proto-repl"&gt;Proto-Repl&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I believe those are the most used editors with good REPL integrations, at least according to the latest &lt;a href="https://clojure.org/news/2020/02/20/state-of-clojure-2020"&gt;State of Clojure&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are a complete newcomer a good idea is to choose an editor that you already know. Install the Repl plugin to that editor and learn to use it. Especially learn how to send the forms (Clojure expressions) from the editor to the REPL for evaluation.&lt;/p&gt;

&lt;p&gt;Once you have a working development environment start learning Clojure. A good resource is &lt;a href="https://www.braveclojure.com/"&gt;Brave Clojure&lt;/a&gt;. And remember to do the exercises and experiments with your editor and send the forms to REPL for evaluation using your hotkey!&lt;/p&gt;

&lt;h3&gt;
  
  
  Use a REPL Scratch File
&lt;/h3&gt;

&lt;p&gt;I believe that newcomers often write their experiments in the REPL editor, at least I did. Don’t do it. Instead, create a scratch file and write your experiments there. I watched some Stuart Halloway presentation in which he told that he has a dedicated Clojure directory in which he has written all his Clojure experimentations for several years - must be pretty nice to be able to &lt;a href="https://en.wikipedia.org/wiki/Grep"&gt;grep&lt;/a&gt; when looking for something you have written years ago. I have another habit. I have in my &lt;code&gt;~/.clojure/deps.edn&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:aliases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="no"&gt;:kari&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:extra-paths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"scratch"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="no"&gt;:extra-deps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hashp/hashp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0.1.1"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="n"&gt;com.gfredericks/debug-repl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0.0.11"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="n"&gt;djblue/portal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0.6.1"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Focus on row &lt;code&gt;:kari {:extra-paths ["scratch"]&lt;/code&gt;. This line adds directory &lt;code&gt;scratch&lt;/code&gt; into the Clojure path in any project in which I add my personal profile &lt;code&gt;kari&lt;/code&gt;, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clj &lt;span class="nt"&gt;-M&lt;/span&gt;:dev:test:common:backend:kari &lt;span class="nt"&gt;-m&lt;/span&gt; nrepl.cmdline &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I.e. All projects have various aliases that you need to use when starting the REPL. I just add my personal alias &lt;code&gt;kari&lt;/code&gt; at the end and then I’m able to create &lt;code&gt;scratch.clj&lt;/code&gt; file in that &lt;code&gt;scratch&lt;/code&gt; directory and write all project specific Clojure experimentation there. I think this is also a nice way - I have all my project related Clojure experimentation in one place. A screenshot from my IntelliJ IDEA showing the &lt;code&gt;scratch&lt;/code&gt; directory and two scratch files: one for backend experimentation (&lt;code&gt;scratch.clj&lt;/code&gt; - content showing in the editor window) and one for frontend experimentation (&lt;code&gt;scratch-cljs.cljs&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Roa3ai8S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-26-clojure-power-tools-part-1_img_2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Roa3ai8S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-26-clojure-power-tools-part-1_img_2.png" alt="Scratch directory in IntelliJ IDEA"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Rich Comments
&lt;/h3&gt;

&lt;p&gt;Clojurians use so-called &lt;code&gt;rich comments&lt;/code&gt;. These are typically small code snippets at the end of the file surrounded in &lt;code&gt;comment&lt;/code&gt; form. This way the Clojure code inside the rich comment is valid Clojure code that &lt;em&gt;is read&lt;/em&gt; by the Clojure reader but it is not &lt;em&gt;evaluated&lt;/em&gt;. Therefore you can put all kinds of namespace specific experimentation or code examples in the rich comment block. Example from one of my exercises: &lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/src/clj/simpleserver/service/domain/domain_postgres.clj#L48"&gt;domain_postgres.clj&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;comment&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;simpleserver.test-config/go&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;simpleserver.test-config/halt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;simpleserver.test-config/test-env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;simpleserver.test-config/test-env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:db&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sql-get-products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:pg-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}))&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;simpleserver.test-config/test-env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:db&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sql-get-product&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:pg-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:p-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)}))&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s pretty easy later on to remember stuff in those rich comments. E.g. in the above-mentioned example I have some functions to start/halt the Integrant test state and some functions in which I test some SQL operations in the domain using the database connection from the test state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Defs Inside Functions When Debugging
&lt;/h3&gt;

&lt;p&gt;This is a classic Clojure trick. If you are wondering what is happening inside some function - just take a snapshot of the most important entities in that function. Let’s look at the same file, &lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/src/clj/simpleserver/service/domain/domain_postgres.clj#L37"&gt;domain_postgres.clj&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;if-let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sql-get-product&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:pg-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg-id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:p-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p-id&lt;/span&gt;&lt;span class="p"&gt;)})]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wonder what kind of data structure is returned from the function and bound to &lt;code&gt;kv&lt;/code&gt;? Let’s see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;if-let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sql-get-product&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:pg-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg-id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:p-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p-id&lt;/span&gt;&lt;span class="p"&gt;)})]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;todo-kv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="no"&gt;:id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic is in the &lt;code&gt;let&lt;/code&gt; I added: &lt;code&gt;(let [_ (def todo-kv kv)]&lt;/code&gt;: we just create a &lt;em&gt;var&lt;/em&gt; (&lt;code&gt;todo-kv&lt;/code&gt;) and bind the value of &lt;code&gt;kv&lt;/code&gt; to it.&lt;/p&gt;

&lt;p&gt;Then let’s run the tests so that this function gets called and after the test let’s examine &lt;code&gt;todo-kv&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="n"&gt;todo-kv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:pg_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Test Once Upon a Time in the West"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:price&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;14.40&lt;/span&gt;&lt;span class="n"&gt;M,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:a_or_d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Leone"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:year&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1968&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:country&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Italy-USA"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:g_or_l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Western"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Hashp for Printing Values
&lt;/h3&gt;

&lt;p&gt;Sometimes you want to preserve the data structure and examine it in your scratch file - then the &lt;code&gt;def&lt;/code&gt; trick described in the previous chapter is all you need. But sometimes you just want to see the value. You could use e.g. &lt;a href="https://clojure.github.io/clojure/clojure.pprint-api.html"&gt;clojure.pprint&lt;/a&gt; to print the value of the var but there is another nice power tool for it: &lt;a href="https://github.com/weavejester/hashp"&gt;hashp&lt;/a&gt;. E.g. in your rich comment evaluate: &lt;code&gt;(require '[hashp.core])&lt;/code&gt; and then add &lt;code&gt;#p&lt;/code&gt; before the function you are interested in, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;if-let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sql-get-product&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:pg-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg-id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:p-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p-id&lt;/span&gt;&lt;span class="p"&gt;)})]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Evaluate the namespace (or reset Integrant state or something similar) and run the tests and you will see an output like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="n"&gt;Testing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;simpleserver.service.domain.domain-test&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;simpleserver.service.domain.domain-postgres.PostgresR/fn&lt;/span&gt;&lt;span class="no"&gt;:37&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sql-get-product&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:p-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p-id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:pg-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg-id&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:a_or_d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Leone"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:country&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Italy-USA"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:g_or_l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Western"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:pg_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:price&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;14.40&lt;/span&gt;&lt;span class="n"&gt;M,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Test Once Upon a Time in the West"&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="no"&gt;:year&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1968&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Panel to Visualize Data
&lt;/h3&gt;

&lt;p&gt;This is pretty cool and I learned this only this week from one Metosin Clojure guru (aah, I need to remember to write a chapter: “Find a Clojure Shop”).&lt;/p&gt;

&lt;p&gt;If you have a complex domain in which you have a lot of data with a lot of children and those children having children and so on - it is a bit daunting to try to visualize this in the REPL output window or using the REPL to navigate in the data. &lt;a href="https://github.com/djblue/portal"&gt;portal&lt;/a&gt; to the rescue!&lt;/p&gt;

&lt;p&gt;Write the following forms in your scratch file or in the rich comment of your namespace - I’ll use the same &lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/src/clj/simpleserver/service/domain/domain_postgres.clj"&gt;domain_postgres.clj&lt;/a&gt; file as an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;portal.api&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;portal-api&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;portal.api/open&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;portal.api/tap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tap&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;simpleserver.test-config/test-env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:db&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sql-get-products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:pg-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)})))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, the &lt;code&gt;(portal.api/open)&lt;/code&gt; opens the visualization window, &lt;code&gt;(portal.api/tap)&lt;/code&gt; adds portal as a tap, and then using &lt;a href="https://clojuredocs.org/clojure.core/tap%3E"&gt;tap&amp;gt;&lt;/a&gt; you can send data to the visualization window. See example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EqsO265Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-26-clojure-power-tools-part-1_img_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EqsO265Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-26-clojure-power-tools-part-1_img_3.png" alt="IntelliJ IDEA and Cursive"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a very simple example. But imagine that there are hundreds of lines of data, vectors, maps, more vectors and maps inside them, etc. A visualization tool like Portal is a must-have tool. There are other similar visualization tools - the Clojure itself has one nowadays: &lt;a href="https://clojure.github.io/clojure/clojure.inspector-api.html"&gt;clojure.inspector&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Find a Clojure Shop
&lt;/h3&gt;

&lt;p&gt;If you really want to learn Clojure I strongly recommend you to find a job in which you are able to spend 8 hours every workday with Clojure - with other seasoned Clojurians. I really can’t overemphasize how important this is. I learned the basic stuff of Clojure alone, just reading books, googling stuff, doing exercises. I had a long career in the corporate world and one day I realized that I have paid my mortgage and my kids are adults and I can basically do whatever I like with my life - and I asked myself: What do you want really want to do? I realized that I want to do Clojure &amp;amp; cloud projects. I contacted the most prominent Clojure shops in Finland and got a job at the best of them, &lt;a href="https://www.metosin.fi"&gt;Metosin&lt;/a&gt;. Damn, it’s been a jolly ride with Metosin. The company is full of world-class Clojurians, just look at some extremely popular Metosin libraries like &lt;a href="https://github.com/metosin/reitit"&gt;reitit&lt;/a&gt;. And I got a chance to work on the same project with guys who build these libraries. I should be paying them instead of the company paying me. What incredible luck. In a few months, I have already learned so much from these guys. And learning more and more every day.&lt;/p&gt;

&lt;p&gt;So. If you really want to learn Clojure, it is really, really important to find a great Clojure shop where you can learn from the best.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;There are a lot of other Clojure tricks and tools - maybe I’ll write “Clojure Power Tools Part 2” blog post in the future.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The writer is working at Metosin using Clojure in cloud projects. If you are interested to start a Clojure project in Finland or you are interested to get Clojure training in Finland you can contact me by sending email to my Metosin email address or contact me via LinkedIn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Kari Marttila&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kari Marttila’s Home Page in LinkedIn: &lt;a href="https://www.linkedin.com/in/karimarttila/"&gt;https://www.linkedin.com/in/karimarttila/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>clojure</category>
      <category>repl</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Clojure Re-Frame Exercise</title>
      <dc:creator>Kari Marttila</dc:creator>
      <pubDate>Thu, 15 Oct 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/karimarttila/clojure-re-frame-exercise-50i2</link>
      <guid>https://dev.to/karimarttila/clojure-re-frame-exercise-50i2</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4QG9Dt8z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-15-clojure-re-frame-exercise_img_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4QG9Dt8z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-15-clojure-re-frame-exercise_img_1.png" alt="IntelliJ IDEA and Cursive"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Clojure Re-Frame Exercise in IntelliJ IDEA / Cursive IDE.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;I created a simple frontend for my SimpleServer exercise a couple of years ago: &lt;a href="https://github.com/karimarttila/clojure/tree/master/clj-ring-cljs-reagent-demo/simple-frontend"&gt;SimpleFrontend&lt;/a&gt;. In that exercise I used &lt;a href="https://reagent-project.github.io/"&gt;Reagent library&lt;/a&gt; which is a minimalistic &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt; wrapper for ClojureScript - I have documented the exercise in &lt;a href="https://www.karimarttila.fi/clojure/2018/04/22/become-a-full-stack-developer-with-clojure-and-clojurescript.html"&gt;Become a Full Stack Developer with Clojure and ClojureScript!&lt;/a&gt; blog post. At my company, &lt;a href="https://www.metosin.fi/"&gt;Metosin&lt;/a&gt;, we also use quite a lot another ClojureScript framework that provides some additional utilities like state management - &lt;a href="https://day8.github.io/re-frame/"&gt;Re-Frame&lt;/a&gt;. In this blog post I describe my recent Clojure exercise when I re-implemented the SimpleFrontend using Re-Frame.&lt;/p&gt;

&lt;p&gt;The exercise can be found in my &lt;a href="https://github.com/karimarttila/clojure"&gt;Clojure&lt;/a&gt; repo in directory &lt;a href="https://github.com/karimarttila/clojure/tree/master/webstore-demo/re-frame-demo"&gt;re-frame&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is ClojureScript?
&lt;/h3&gt;

&lt;p&gt;I wrote this frontend exercise using ClojureScript. &lt;a href="https://clojurescript.org/"&gt;ClojureScript&lt;/a&gt; is a Clojure dialect that transpiles to Javascript. So, if you are a Clojurian, you love functional paradigm, immutable data structures, and clean code - you can use your favorite functional language both in the backend (Clojure) and frontend (ClojureScript).&lt;/p&gt;

&lt;p&gt;And what is &lt;a href="https://clojure.org/"&gt;Clojure&lt;/a&gt;? Possibly the most beautiful language in the world. Once you learn it, you never go back.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are Reagent and Re-Frame?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://reagent-project.github.io/"&gt;Reagent library&lt;/a&gt; is a minimalistic &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt; wrapper for ClojureScript. With Reagent you can easily create React components using &lt;a href="https://github.com/weavejester/hiccup"&gt;Hiccup&lt;/a&gt; which provides a very “Clojurian” way to represent HTML code. Reagent also introduces its own version of the &lt;a href="https://clojure.org/reference/atoms"&gt;Clojure Atom&lt;/a&gt; - any component that dereferences a Reagent atom will be automatically re-rendered when there are changes in that atom.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://day8.github.io/re-frame/"&gt;Re-Frame&lt;/a&gt; provides a framework that uses Reagent internally. The Re-Frame framework adds various capabilities e.g. an application database into which you can store the application state and an easy way to subscribe to the elements in the application database and get automatic re-rendering when those elements change in the application database. I’m not going to explain the Re-Frame framework in more detail - the &lt;a href="https://day8.github.io/re-frame/"&gt;Re-Frame&lt;/a&gt; website provides excellent documentation for the framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tooling
&lt;/h3&gt;

&lt;p&gt;I did most of the tooling as I have learned to use them in the Metosin frontend projects (by the way, if you are a Clojurian and you want to learn from the best - join Metosin).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shadow-cljs.&lt;/strong&gt; &lt;a href="https://github.com/thheller/shadow-cljs"&gt;Shadow-cljs&lt;/a&gt; is a build tool for ClojureScript. Shadow-cljs is really nice to use - you get to see your changes both in the ClojureScript code and Sass code in real-time in the browser. You can also get a &lt;a href="https://clojure.org/guides/repl/introduction"&gt;REPL&lt;/a&gt; which runs your ClojureScript code in the browser (see the example in the picture above - I have a scratch file in which I have written some ClojureScript code and I send the forms for evaluation to the REPL running in the browser - you can e.g. examine the re-frame app db using the REPL). Shadow-cljs configuration, see: &lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/shadow-cljs.edn"&gt;shadow-cljs.edn&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Justfile.&lt;/strong&gt; I use &lt;a href="https://github.com/casey/just"&gt;Just&lt;/a&gt; to provide commandline interface to setup the project fixture before development, e.g. &lt;code&gt;just postgres&lt;/code&gt;: start the PostgreSQL development database, &lt;code&gt;just backend-kari&lt;/code&gt;: start backend repl with my own Clojure config, &lt;code&gt;just frontend-kari&lt;/code&gt;: start frontend etc. Justfile configuration, see: &lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/Justfile"&gt;Justfile&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Npm.&lt;/strong&gt; Shadow-cljs integrates nicely with &lt;a href="https://www.npmjs.com/"&gt;Npm&lt;/a&gt;. See &lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/package.json"&gt;package.json&lt;/a&gt; for a list of npm packages I’m using in this exercise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sass.&lt;/strong&gt; I’m using &lt;a href="https://sass-lang.com/"&gt;Sass&lt;/a&gt; which is an extension language for &lt;a href="https://www.w3.org/Style/CSS/"&gt;CSS&lt;/a&gt;. The Sass in this exercise is pretty minimalistic - the purpose of this exercise was not to create a beautiful frontend but to learn to use the tooling and re-frame. See example in &lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/src/sass/main.scss"&gt;main.scss&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deps.edn.&lt;/strong&gt; In the &lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/deps.edn"&gt;deps.edn&lt;/a&gt; file you can find the &lt;code&gt;frontend&lt;/code&gt; alias which gives the dependencies for the frontend in this exercise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metosin Reagent-dev-tools.&lt;/strong&gt; I used quite a lot the excellent &lt;a href="https://github.com/metosin/reagent-dev-tools"&gt;Metosin Reagent-dev-tools&lt;/a&gt; (as you can see in the picture below). I tried the &lt;a href="https://github.com/day8/re-frame-10x"&gt;re-frame-10x&lt;/a&gt; tool as well, but I liked more the Metosin Reagent-dev-tool’s visual layout (and I’m a company man - you eat your own dog food). The Metosin Reagent-dev-tool was one of the most important development and debugging tools during this exercise. Once you configure the tool to show the Re-frame application db state you get a nice tree view to the app-db:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="no"&gt;:export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;js/console.log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ENTER init"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/dispatch-sync&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;::initialize-db&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dev-tools/start!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:state-atom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;re-frame.db/app-db&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dev-setup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Routing
&lt;/h3&gt;

&lt;p&gt;For routing, I used &lt;a href="https://github.com/metosin/reitit"&gt;Reitit&lt;/a&gt; both in the backend and in the frontend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;routes-dev&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;::sf-state/home&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:view&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;home-page&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:link-text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Home"&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:controllers&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;js/console.log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Entering home page"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:stop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;js/console.log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Leaving home page"&lt;/span&gt;&lt;span class="p"&gt;))}]}]&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"signin"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;::sf-state/signin&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:view&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sf-signin/signin-page&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:link-text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Sign-In"&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:controllers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since I’m a company man I’m using Metosin libraries, of course. I examined Metosin &lt;a href="https://github.com/metosin/reitit/tree/master/examples/frontend-re-frame"&gt;Reitit + re-frame&lt;/a&gt; example and based my work on it. The example provides a simple solution for frontend routing based on the &lt;a href="https://github.com/metosin/reitit"&gt;Reitit library&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Re-Frame
&lt;/h3&gt;

&lt;p&gt;I created a dedicated ClojureScript namespace for every view (see directory &lt;a href="https://github.com/karimarttila/clojure/tree/master/webstore-demo/re-frame-demo/src/cljs/simplefrontend"&gt;simplefrontend&lt;/a&gt; ). Using Hiccup, Reagent and Re-frame you can quickly create React application even though you know almost nothing of React itself. Example &lt;a href="https://github.com/karimarttila/clojure/blob/master/webstore-demo/re-frame-demo/src/cljs/simplefrontend/products.cljs"&gt;products.cljs&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products-page&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"Products view."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; NOTE: This is the current-route given as parameter to the view. You can get the pgid also from :path-params.&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sf-util/clog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ENTER products-page, match"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;path&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:parameters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pgid&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;path&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;pgid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sf-util/clog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sf-util/clog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"pgid"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgid&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;products-data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/subscribe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;::products-data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgid&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;product-group-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/subscribe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;::product-group-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgid&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;if-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products-data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/dispatch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;::get-products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgid&lt;/span&gt;&lt;span class="p"&gt;]))]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:h3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Products - "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product-group-name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div.sf-pg-container&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;products-table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products-data&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:button.sf-basic-button&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:on-click&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.preventDefault&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/dispatch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;::sf-state/navigate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;::sf-state/home&lt;/span&gt;&lt;span class="p"&gt;]))}&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="s"&gt;"Go to home"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sf-util/debug-panel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:products-data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products-data&lt;/span&gt;&lt;span class="p"&gt;})]))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the page, you can subscribe to &lt;a href="https://day8.github.io/re-frame/subscriptions/"&gt;re-frame subscriptions&lt;/a&gt; (example: &lt;code&gt;products-data @(re-frame/subscribe [::products-data pgid])&lt;/code&gt; ). Whenever that data change the React component gets re-rendered. The paradigm is really easy to understand and work with.&lt;/p&gt;

&lt;p&gt;You can trigger &lt;a href="https://github.com/day8/re-frame/blob/master/docs/Coeffects.md"&gt;co-effects&lt;/a&gt; to populate data in your app-db, example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/reg-event-fx&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;::get-products&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg-id&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sf-util/clog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"get-product, pg-id"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg-id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sf-http/http-get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/api/products/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg-id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;::ret-ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;::ret-failed&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this code snippet we are sending a get request to the backend to get the products for a given product group id. Then the &lt;code&gt;::ret-ok&lt;/code&gt; handler gets triggered when we get the response from the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/reg-event-db&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;::ret-ok&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;res-body&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sf-util/clog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"reg-event-db ok: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;res-body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pgid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:pg-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;res-body&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;assoc-in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:response&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:res-body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;res-body&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;assoc-in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;res-body&lt;/span&gt;&lt;span class="p"&gt;))))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see we are storing the data in the re-frame app-db. The actual data needed is stored in map in hierarchy &lt;code&gt;[:products :data pgid]&lt;/code&gt; but I’m storing the whole response there also just for development, learning and debugging purposes.&lt;/p&gt;

&lt;p&gt;Ok, now we have the data in the re-frame application database. Then in the view you just make a subscription for that data: &lt;code&gt;products-data @(re-frame/subscribe [::products-data pgid])&lt;/code&gt;. The actual subscription registration is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/reg-sub&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="no"&gt;::products-data&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sf-util/clog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"::products-data, params"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pgid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;second&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:data&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sf-util/clog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"products-data"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pgid&lt;/span&gt;&lt;span class="p"&gt;]))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, we just fetch the data from the re-frame application database and return it to anyone who subscribes to that data. When the data changes in the re-frame application database the React component that uses that data gets re-rendered.&lt;/p&gt;

&lt;h3&gt;
  
  
  Development Flow
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;ClojureScript REPL.&lt;/strong&gt; I’m an old backend programmer and I haven’t done that much frontend development, not to speak of Single Page Application development. But using ClojureScript, Hiccup, Reagent, Re-Frame, and Shadow-cljs made the journey quite effortless. In the picture at the beginning of this article, you can see a screenshot of my IntelliJ IDEA / &lt;a href="https://cursive-ide.com/"&gt;Cursive&lt;/a&gt; setup. When I’m implementing some Clojure application I always keep a Clojure REPL running (as all Lisp programmers - the REPL is an integral part of Lisp programming). In this exercise I kept two REPLs running: the backend REPL (running in the JVM, state managed by &lt;a href="https://github.com/weavejester/integrant"&gt;Integrant&lt;/a&gt;, see my other blog post about that: &lt;a href="https://www.karimarttila.fi/clojure/2020/09/07/clojure-integrant-exercise.html"&gt;Clojure Integrant Exercise&lt;/a&gt; ) and the frontend REPL (running in the browser). I guess most of the Javascript/Typescript programmers can’t understand how powerful a Lisp REPL can be in frontend development, you just have to use it yourself to understand it fully. If you look at the scratch file and the REPL output window you can see that you can use any Javascript commands (console, alert…) and also examine and modify the running state of the frontend application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metosin Reagent-dev-tools.&lt;/strong&gt; I already mentioned the &lt;a href="https://github.com/metosin/reagent-dev-tools"&gt;Metosin Reagent-dev-tools&lt;/a&gt; in the Tooling chapter. Reagent-dev-tools was an integral part of the development workflow. You could see in real-time the current state of the application in the re-frame application database using the tool tree view. An excellent tool for development and debugging, I highly recommend it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chrome DevTools.&lt;/strong&gt; I’m not that fluent with the &lt;a href="https://developers.google.com/web/tools/chrome-devtools"&gt;Chrome DevTools&lt;/a&gt; but I used it quite a lot. The tool is really nice: you can print in the console log various debugging information, you can inspect elements and debug various CSS related issues, you can inspect the http requets and responses etc. But I guess all frontend developers already know this stuff.&lt;/p&gt;

&lt;p&gt;The following picture shows the Metosin Reagent-dev-tools (bottom part of the picture) and the Chrome DevTools (right side).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sArO8eBh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-15-clojure-re-frame-exercise_img_2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sArO8eBh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-15-clojure-re-frame-exercise_img_2.png" alt="Browser view"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Browser view and development tools.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because it was so easy I also implemented a very simple Debug panel for my own debugging purposes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;debug-panel&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"Debug panel - you can use this panel in any view to show some page specific debug data."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;re-frame/subscribe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;::sf-state/debug&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;js/console.log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ENTER debug-panel, debug: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:div.sf-debug-panel&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:hr.sf-debug-panel.hr&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:h3.sf-debug-panel.header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"DEBUG-PANEL"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:pre.sf-debug-panel.body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;with-out-str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;clojure.pprint/pprint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))]])))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can use this debug-panel in any view adding it as a React component e.g. to the bottom of the page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;login-page&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"Login view."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sf-util/debug-panel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:login-data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;login-data&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="no"&gt;:ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="no"&gt;:msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="no"&gt;:r-body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r-body&lt;/span&gt;&lt;span class="p"&gt;})]))))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;… and show there whatever data is important for debugging purposes in that view.&lt;/p&gt;

&lt;p&gt;The following picture shows debug panel in action in that page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cmCDKXkM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-15-clojure-re-frame-exercise_img_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cmCDKXkM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-10-15-clojure-re-frame-exercise_img_3.png" alt="My Debug Panel"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Custom Debug Panel.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Styles
&lt;/h3&gt;

&lt;p&gt;I’m using raw &lt;a href="https://en.wikipedia.org/wiki/CSS"&gt;CSS&lt;/a&gt; with &lt;a href="https://sass-lang.com/"&gt;Sass&lt;/a&gt;. I considered using some React UI library like &lt;a href="https://ant.design/"&gt;Ant Design&lt;/a&gt; or &lt;a href="https://material-ui.com/"&gt;Material UI&lt;/a&gt; but then decided to use raw CSS/Sass instead - the rationale being that this is a re-frame exercise and I didn’t want to spend time in this exercise learning some UI library. I might do another exercise later on in which I convert this frontend to use e.g. Ant Design which has a nice ClojureScript wrapper (&lt;a href="https://gitlab.com/synqrinus/syn-antd"&gt;syn-antd&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;A Sass example giving the style for the custom debug panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sass"&gt;&lt;code&gt;&lt;span class="nc"&gt;.sf-debug-panel&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.hr&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100px&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.header&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;28px&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.body&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issues
&lt;/h3&gt;

&lt;p&gt;Some issues I need to fix later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;index.html in uri.&lt;/strong&gt; I’d like to get rid of the &lt;code&gt;index.html&lt;/code&gt; in uri, e.g. &lt;code&gt;http://localhost:6161/index.html#/products/1&lt;/code&gt; should be &lt;code&gt;http://localhost:6161/#/products/1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;edn data in http requests.&lt;/strong&gt; I tried passing &lt;code&gt;edn&lt;/code&gt; instead of &lt;code&gt;json&lt;/code&gt; in http requests but I had some issues with it (e.g. the response body came just fine as edn, but re-frame error handler got triggered even though the reply was &lt;code&gt;200&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Live Reloading
&lt;/h3&gt;

&lt;p&gt;Live reloading worked really well. If you make any changes to any cljs or sass file, you could see the changed React components re-rendering in that view in realtime. Using the tooling and libraries mentioned in this exercise was a really nice experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;It was an interesting journey to do this exercise. As an old backend developer frontends have always been my Achilles’ heel. When implementing this frontend exercise I needed to learn about the ClojureScript tooling, React (just a bit), Reagent and Re-Frame and the overall workflow related to the frontend development - very beneficial for my future frontend projects. If you are an old backend programmer like me and you would like to have an easy environment to start working with Single Page Application frontends - ClojureScript with Reagent and Re-frame is a good choice.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The writer is working at Metosin using Clojure in cloud projects. If you are interested to start a Clojure project in Finland or you are interested to get Clojure training in Finland you can contact me by sending email to my Metosin email address or contact me via LinkedIn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Kari Marttila&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kari Marttila’s Home Page in LinkedIn: &lt;a href="https://www.linkedin.com/in/karimarttila/"&gt;https://www.linkedin.com/in/karimarttila/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>clojure</category>
      <category>clojurescript</category>
      <category>languages</category>
    </item>
    <item>
      <title>Dygma Raise Keyboard Reflections Part 1</title>
      <dc:creator>Kari Marttila</dc:creator>
      <pubDate>Mon, 28 Sep 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/karimarttila/dygma-raise-keyboard-reflections-part-1-3f42</link>
      <guid>https://dev.to/karimarttila/dygma-raise-keyboard-reflections-part-1-3f42</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qKofzJdM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-09-28-dygma-raise-keyboard-reflections-part-1_img_1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qKofzJdM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-09-28-dygma-raise-keyboard-reflections-part-1_img_1.jpg" alt="My Dygma Raise"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;My Dygma Raise Keyboard.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;I have been using &lt;a href="https://www.microsoft.com/accessories/fi-fi/products/keyboards/natural-ergonomic-keyboard-4000/b2m-00026"&gt;Microsoft Natural Keyboard 4000&lt;/a&gt; for several years and it has served my programming and writing needs pretty well with my Ubuntu laptop &lt;code&gt;.Xkeymap&lt;/code&gt; configurations (more about that later). But last spring I followed &lt;a href="https://koodiklinikka.fi/"&gt;Koodiklinikka Slack’s&lt;/a&gt; “nappaimistot” (“keyboards” in English) channel in order to read experiences regarding mechanical keyboards. There I read some experiences regarding the &lt;a href="https://dygma.com/"&gt;Dygma Raise Keyboard&lt;/a&gt;. I ordered the Dygma Raise keyboard while the company was manufacturing the second batch of the keyboard. I ordered the keyboard in May and I got the keyboard a couple of days ago. Now that I have spent a couple of days configuring the keyboard and using it I thought that it might be beneficial for other programmers to write a bit about my experiences regarding the Dygma Raise - because I fell in love with it immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build Quality
&lt;/h3&gt;

&lt;p&gt;The build quality of the Dygma Raise Keyboard seems to be really good. The chassis is very rigid and sturdy. All keys feel very consistent and there is an overall feeling of premium quality when using the keyboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ergonomics
&lt;/h3&gt;

&lt;p&gt;You can split the keyboard into two halves and place the halves on your table as you wish. I keep some space between the halves and also have a small angle between the halves so that my shoulders and wrists are in a pretty natural position on my table. I also have a large elbow support on my table. All these together provide pretty good ergonomics for my programming. Not to talk about the ergonomics regarding the key presses but more about that in the following chapters. Dygma provides in its website a more detailed description regarding the &lt;a href="https://dygma.com/pages/ergonomic-split-keyboard"&gt;Dygma Raise Ergonomics&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Led Lights
&lt;/h3&gt;

&lt;p&gt;I haven’t found any real use for the various colors - I do programming. I type with all my ten fingers - I took a typing lesson in the Finnish Elementary school about 40 years ago and have been typing with 10 fingers ever since - so, I don’t look at my keyboard at all when typing. But the colors are kind of nice anyway. Maybe gamers use these colors for some real tasks or they just “look cool”, I don’t know. Anyway, I don’t mind the colors. I did use the Bezecor to categorize certain buttons with consistent colors (see the two pictures below). And it is also kind of nice that the &lt;code&gt;Shift to 1&lt;/code&gt; button also changes the color in the frame of the halves - a visual aid that we are now in the next layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layout
&lt;/h3&gt;

&lt;p&gt;One of the most important reasons to buy Dygma Raise was that I could order it with the Nordic layout. There are other split keyboards in the market but some of them provided just &lt;code&gt;ANSI&lt;/code&gt; layout, and the rest didn’t provide the kind of programmability of the keyboard like Dygma Raise. The keyboard layout itself is really important to me. There are certain special characters in the Nordic languages (e.g. &lt;code&gt;Ä&lt;/code&gt; and &lt;code&gt;Ö&lt;/code&gt;) and I want those keys to be in their right places so that I can write Finnish text fluently both using the laptop keyboard and my external keyboard and I don’t need to remember that those letters are in different positions in different keyboards. There are many different ANSI and ISO layouts available for the Dygma Raise - consult the Dygma Raise website for the options.&lt;/p&gt;

&lt;h3&gt;
  
  
  Switches
&lt;/h3&gt;

&lt;p&gt;Dygma Raise is a mechanical keyboard and you can order the kind of mechanical switches you like. Dygma Raise is my first mechanical keyboard for recent years so I ordered rather standard switches not to have too extreme experience to start with: &lt;code&gt;Cherry MX Brown&lt;/code&gt;. Dygma Raise provides a good &lt;a href="https://dygma.com/blogs/stories/the-ultimate-guide-to-mechanical-keyboard-switches-for-2019"&gt;introduction to the mechanical switches&lt;/a&gt; - I really recommend reading it before placing the order for your Dygma Raise. All Dygma Raise switches are swappable so you can change the switches later on if you think that you want to experiment with different switches. I kind of like the “clicky” touch of the &lt;code&gt;Cherry MX Brown&lt;/code&gt; switches but I might next try a bit “lighter” touch (but with a tactile feeling with a clear click sound).&lt;/p&gt;

&lt;h3&gt;
  
  
  My Linux Keymap
&lt;/h3&gt;

&lt;p&gt;Before understanding how I configured Dygma Raise I need to tell the reader a bit about my Linux key mapping. For a programmer, the &lt;code&gt;CapsLock&lt;/code&gt; key is a totally useless key. In the Nordic layout the various parentheses ( &lt;code&gt;{&lt;/code&gt;, &lt;code&gt;[&lt;/code&gt;, &lt;code&gt;[&lt;/code&gt;, &lt;code&gt;}&lt;/code&gt;) are in the first row behind &lt;code&gt;7&lt;/code&gt;, &lt;code&gt;8&lt;/code&gt;, &lt;code&gt;9&lt;/code&gt; and &lt;code&gt;0&lt;/code&gt; keys when you press &lt;code&gt;Alt-Gr&lt;/code&gt; key at the same time. The problem is that this &lt;code&gt;Alt-Gr&lt;/code&gt; key is in a really awkward position in the last row behind your right thumb - practically impossible to press this &lt;code&gt;Alt-Gr&lt;/code&gt; and &lt;code&gt;7&lt;/code&gt;, &lt;code&gt;8&lt;/code&gt;, &lt;code&gt;9&lt;/code&gt; and &lt;code&gt;0&lt;/code&gt; keys without getting a Carpal Tunnel Syndrome in your wrists after a few years. Therefore many Nordic programmers tend to configure these special parentheses in new positions. My solution was to keep the special character keys where they are but to configure &lt;code&gt;CapsLock&lt;/code&gt; key as &lt;code&gt;Alt-Gr&lt;/code&gt; key since hitting the &lt;code&gt;CapsLock&lt;/code&gt; key with my left little finger and at the same time hitting &lt;code&gt;7&lt;/code&gt;, &lt;code&gt;8&lt;/code&gt;, &lt;code&gt;9&lt;/code&gt; and &lt;code&gt;0&lt;/code&gt; keys with my right hand fingers to produce &lt;code&gt;{&lt;/code&gt;, &lt;code&gt;[&lt;/code&gt;, &lt;code&gt;[&lt;/code&gt;, &lt;code&gt;}&lt;/code&gt; was relatively easy. Since I could now configure keys with &lt;code&gt;CapsLock&lt;/code&gt; for special functions, I also configured &lt;code&gt;I&lt;/code&gt;, &lt;code&gt;J&lt;/code&gt;, &lt;code&gt;K&lt;/code&gt; and &lt;code&gt;L&lt;/code&gt; keys to be arrow keys with &lt;code&gt;CapsLock&lt;/code&gt;, &lt;code&gt;D&lt;/code&gt; to be delete with &lt;code&gt;CapsLock&lt;/code&gt; (as &lt;code&gt;Ctrl&lt;/code&gt;+&lt;code&gt;D&lt;/code&gt; is delete in Emacs) etc.&lt;/p&gt;

&lt;p&gt;A screenshot regarding some of these configurations (&lt;code&gt;.Xkeymap&lt;/code&gt; file):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Mrfo-bIC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-09-28-dygma-raise-keyboard-reflections-part-1_img_2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Mrfo-bIC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-09-28-dygma-raise-keyboard-reflections-part-1_img_2.jpg" alt="My Linux .Xkeymap"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring Dygma Raise: The Bazecor Software
&lt;/h3&gt;

&lt;p&gt;Ok, let’s go back to Dygma Raise and how my Linux Keymap works now with Dygma Raise. It turned out that Linux Keymap and Dygma Raise is a match made in heaven.&lt;/p&gt;

&lt;p&gt;The configurability of the Dygma Raise keyboard is just superb. This is something that I’m just beginning to realize after configuring the keyboard for a couple of days. You can use your imagination as much as you like and it seems that Dygma does not restrict you in any way.&lt;/p&gt;

&lt;p&gt;Every key is programmable. And there are ten layers and in all of those layers, you can program every key or configure the key to be “transparent” - meaning it will function as the key in the previous layer. I have configured two layers using a rule of thumb that I want all special characters to be as near the base row (&lt;code&gt;ASDF&lt;/code&gt; &amp;amp; &lt;code&gt;JKLÖ&lt;/code&gt;) where I keep my fingers while resting.&lt;/p&gt;

&lt;p&gt;One very neat feature is also that there are eight “thumb keys” instead of one big spacebar. You really should give some thought on how to use these thumb keys since they can be really powerful. Let’s start with the thumb keys. Below you can see my Layer 0 configuration in the Bazecor application:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SKfOI-_o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-09-28-dygma-raise-keyboard-reflections-part-1_img_3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SKfOI-_o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-09-28-dygma-raise-keyboard-reflections-part-1_img_3.jpg" alt="Layer 0"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have played classical guitar some 20 years and now that I look at that picture I realize something regarding the hotkeys I tend to use. Like playing the guitar one has different functions for the left hand and for the right hand: left hand fingers press the strings in various positions on the guitar neck, and right hand fingers pick the strings. The same way I tend to use my keyboard: I press some combination of the &lt;code&gt;Shift&lt;/code&gt;, &lt;code&gt;Ctrl&lt;/code&gt;, &lt;code&gt;Alt&lt;/code&gt; and &lt;code&gt;CapsLock&lt;/code&gt; (remember: my &lt;code&gt;CapsLock&lt;/code&gt; is the &lt;code&gt;Alt-Gr&lt;/code&gt; key) with my left hand fingers, and then I click some key on the right side of the keyboard with my right hand fingers. Therefore I configured my new Dygma Raise so that I have all relevant modifiers in the lefthand side four thumb keys: &lt;code&gt;Alt&lt;/code&gt;, &lt;code&gt;Shift&lt;/code&gt;, &lt;code&gt;Ctrl&lt;/code&gt; and &lt;code&gt;Shift-to-1&lt;/code&gt;. Some examples of how I use these combinations may shed some light to the importance of these new left hand side thumb keys. I program &lt;a href="https://clojure.org/"&gt;Clojure&lt;/a&gt; and I use either &lt;a href="https://www.jetbrains.com/idea/"&gt;IntelliJ IDEA&lt;/a&gt; with excellent &lt;a href="https://cursive-ide.com/"&gt;Cursive&lt;/a&gt; plugin or &lt;a href="https://www.gnu.org/software/emacs/"&gt;Emacs&lt;/a&gt; editor with &lt;a href="https://github.com/clojure-emacs/cider"&gt;Cider&lt;/a&gt; plugin (mostly IDEA/Cursive, though). In Clojure you program quite a lot with &lt;a href="https://clojure.org/guides/repl/introduction"&gt;REPL&lt;/a&gt; and to make the Clojure programming workflow fluent one needs good hotkeys e.g. to reload stuff into the REPL, send namespace to the REPL for evaluation, evaluate certain S-expression in the Clojure code or edit the code, e.g. kill S-expression, &lt;a href="http://danmidwood.com/content/2014/11/21/animated-paredit.html"&gt;slurp/barf&lt;/a&gt;, etc. I’m not going to explain the meaning of those manipulations but let’s just say that I do a lot of them when programming Clojure and there needs to be fast hotkeys for them. Some examples of those hotkeys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kill text from cursor position till the end of line (Emacs kill): &lt;code&gt;Ctrl&lt;/code&gt;+&lt;code&gt;K&lt;/code&gt;. Easy, now I don’t stretch my left hand little finger to the &lt;code&gt;Ctrl&lt;/code&gt; key: I have &lt;code&gt;Ctrl&lt;/code&gt; as a thumb key on both sides.&lt;/li&gt;
&lt;li&gt;Kill S-expression in Clojure code: &lt;code&gt;Shift&lt;/code&gt;+&lt;code&gt;Ctrl&lt;/code&gt;+&lt;code&gt;K&lt;/code&gt;. Adding &lt;code&gt;Shift&lt;/code&gt; since it is logical because it´s kind of &lt;code&gt;kill&lt;/code&gt; like the previous one. Now very easy with Dygma Raise: Just press with left hand thumb the lower thumb keys (&lt;code&gt;Left Shift&lt;/code&gt; and &lt;code&gt;Left Ctrl&lt;/code&gt; - at the same time) and with right hand middle finger press &lt;code&gt;K&lt;/code&gt;. This used to be a bit awkward before Dygma: I had to press the left side &lt;code&gt;Shift&lt;/code&gt; and &lt;code&gt;Ctrl&lt;/code&gt; keys with my left hand little finger…&lt;/li&gt;
&lt;li&gt;Send Clojure form for evaluation to REPL: &lt;code&gt;Alt&lt;/code&gt;+&lt;code&gt;L&lt;/code&gt;: Now very easy: &lt;code&gt;Left Alt&lt;/code&gt; with left hand thumb and &lt;code&gt;L&lt;/code&gt; with right hand ring finger.&lt;/li&gt;
&lt;li&gt;Move cursor to right: &lt;code&gt;CapsLock&lt;/code&gt;+&lt;code&gt;L&lt;/code&gt; (remember: &lt;code&gt;CapsLock&lt;/code&gt; is &lt;code&gt;Alt-Gr&lt;/code&gt; and I have configured &lt;code&gt;I&lt;/code&gt;, &lt;code&gt;J&lt;/code&gt;, &lt;code&gt;K&lt;/code&gt; and &lt;code&gt;L&lt;/code&gt; as arrow keys).&lt;/li&gt;
&lt;li&gt;Because move the cursor to right is &lt;code&gt;CapsLock&lt;/code&gt;+&lt;code&gt;L&lt;/code&gt; it is logical that &lt;a href="http://danmidwood.com/content/2014/11/21/animated-paredit.html"&gt;Slurping&lt;/a&gt; right is &lt;code&gt;CapsLock&lt;/code&gt;+&lt;code&gt;Alt&lt;/code&gt;+&lt;code&gt;L&lt;/code&gt;: easy, left hand: just press &lt;code&gt;Alt&lt;/code&gt; with thumb and &lt;code&gt;CapsLock&lt;/code&gt; with little finger.&lt;/li&gt;
&lt;li&gt;Send text to Slack: &lt;code&gt;Ctrl&lt;/code&gt;+&lt;code&gt;Enter&lt;/code&gt;: Now very easy: left hand thumb and right hand thumb in the lower thumb keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And so on, and so on… I could write a whole series of blog posts regarding these hotkeys - I have a bunch of them for various IDEs, editors, tools, and terminals, but I guess you got the point. I now realize that I play my keyboard a bit like the guitar: both hands tend to have certain functions: I use modifiers with my left hand and give the actual commands with my right hand. (With some exceptions, like &lt;code&gt;CapsLock&lt;/code&gt;+D for delete, but that´s because it is logical to do so, since &lt;code&gt;Ctrl&lt;/code&gt;+&lt;code&gt;D&lt;/code&gt; is delete in emacs and &lt;code&gt;CapsLock&lt;/code&gt; and &lt;code&gt;D&lt;/code&gt; are so close to each other that it is easy to do so…).&lt;/p&gt;

&lt;p&gt;Ok, let’s move on. What’s that Orange &lt;code&gt;Shift to 1&lt;/code&gt; thumb button in the left upper row? It’s the beauty of Dygma Raise: You can shift to the next layer when pressing this key (you can also configure some key so that you move to the next layer instead of activating it only when this key is pressed, but it is better for my personal workflow to have it as “shift”). Let’s see what´s there in the &lt;code&gt;Layer 1&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ST8RX2rQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-09-28-dygma-raise-keyboard-reflections-part-1_img_4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ST8RX2rQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-09-28-dygma-raise-keyboard-reflections-part-1_img_4.jpg" alt="Layer 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this layer I have some Media controllers on the left side just to make playing Spotify easier and also to control volume in meetings: I.e. I press the &lt;code&gt;Shift to 1&lt;/code&gt; with my left hand side thumb and then some media buttons also with my left hand side. This is a bit awkward since I have to use the same hand but as a guitarist, I have pretty flexible fingers and I don’t use these buttons that often. But then the actual beef: &lt;strong&gt;Why do I have the numbers on the right hand side in two rows?&lt;/strong&gt;. The actual reason is not the numbers per se but what’s behind the numbers. The most important row in this layer is the resting position row &lt;code&gt;J&lt;/code&gt;, &lt;code&gt;K&lt;/code&gt;, &lt;code&gt;L&lt;/code&gt;, &lt;code&gt;Ö&lt;/code&gt; =&amp;gt; &lt;code&gt;8&lt;/code&gt;, &lt;code&gt;9&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt;. The reason is that I have mapped the various parentheses behind these numbers, for example: &lt;code&gt;{&lt;/code&gt; =&amp;gt; &lt;code&gt;Alt-Gr&lt;/code&gt;+&lt;code&gt;7&lt;/code&gt;, and because &lt;code&gt;Alt-Gr&lt;/code&gt; is &lt;code&gt;Caps-Lock&lt;/code&gt; it is &lt;code&gt;Caps-Lock&lt;/code&gt;+&lt;code&gt;7&lt;/code&gt;, and because in &lt;code&gt;Layer 0&lt;/code&gt; I can shift to the next &lt;code&gt;Layer 1&lt;/code&gt; with the left hand side button key, and the number &lt;code&gt;7&lt;/code&gt; is not in the upper row in this layer but in the resting row, it is very easy to get &lt;code&gt;{&lt;/code&gt; =&amp;gt; &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;CapsLock&lt;/code&gt; with my left hand fingers (thumb and little finger, I use this very often so it is very easy for me) + &lt;code&gt;H&lt;/code&gt; (layer 0) (which is &lt;code&gt;7&lt;/code&gt; in layer 1). Therefore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;{&lt;/code&gt; =&amp;gt; &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;CapsLock&lt;/code&gt;+&lt;code&gt;H&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[&lt;/code&gt; =&amp;gt; &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;CapsLock&lt;/code&gt;+&lt;code&gt;J&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;]&lt;/code&gt; =&amp;gt; &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;CapsLock&lt;/code&gt;+&lt;code&gt;K&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;}&lt;/code&gt; =&amp;gt; &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;CapsLock&lt;/code&gt;+&lt;code&gt;L&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\&lt;/code&gt; =&amp;gt; &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;CapsLock&lt;/code&gt;+&lt;code&gt;Ö&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(&lt;/code&gt; =&amp;gt; &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;Shift&lt;/code&gt;+&lt;code&gt;J&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;)&lt;/code&gt; =&amp;gt; &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;Shift&lt;/code&gt;+&lt;code&gt;K&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every programmer knows that various parentheses are very much used in any programming language, and Clojure is no exception. On the contrary, you use parentheses very much in Clojure programming: every S-expression needs to be in parenthesis, all literal vectors are inside brackets, all literal maps are inside curly braces, etc. Now with Dygma, I can write all of these parentheses in my right hand resting position.&lt;/p&gt;

&lt;p&gt;How about the upper number row with smaller numbers. The special characters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;!&lt;/code&gt; =&amp;gt; &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;Shift&lt;/code&gt;+&lt;code&gt;U&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"&lt;/code&gt; =&amp;gt; &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;Shift&lt;/code&gt;+&lt;code&gt;I&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#&lt;/code&gt; =&amp;gt; &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;Shift&lt;/code&gt;+&lt;code&gt;O&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;%&lt;/code&gt; =&amp;gt; &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;Shift&lt;/code&gt;+&lt;code&gt;Å&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;amp;&lt;/code&gt; =&amp;gt; &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;Shift&lt;/code&gt;+&lt;code&gt;~&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@&lt;/code&gt; =&amp;gt; &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;CapsLock&lt;/code&gt;+&lt;code&gt;I&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, just the same characters behind those numbers in the Nordic keyboard layout but now just added to the right hand side near the resting row for easier access.&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;Esc&lt;/code&gt; is now &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;CapsLock&lt;/code&gt;+&lt;code&gt;Y&lt;/code&gt;, what a relief. Since e.g. in IntelliJ IDEA when going to the embedded terminal I haven’t found any other way to return to the editor than clicking &lt;code&gt;Esc&lt;/code&gt; twice and &lt;code&gt;Esc&lt;/code&gt; used to be so far away… not anymore with Dygma.&lt;/p&gt;

&lt;p&gt;When I read this article for proof-reading purposes I also realized that I tend to prefer combinations with &lt;em&gt;near the resting positions&lt;/em&gt; (like the &lt;code&gt;Shift to 1&lt;/code&gt;+&lt;code&gt;CapsLock&lt;/code&gt;+&lt;code&gt;Y&lt;/code&gt; I told earlier) over just one keypress but farther away (like &lt;code&gt;Esc&lt;/code&gt; in the upper left side of the keyboard). This is personal, of course. Maybe the reason is that I write with all 10 fingers and it is faster to write when you don’t have to stretch your fingers far away.&lt;/p&gt;

&lt;p&gt;BTW. If you are wondering why I don’t have e.g. &lt;code&gt;Delete&lt;/code&gt; and &lt;code&gt;Backspace&lt;/code&gt; keys configured, I have but not using Bazecor. I have a lot of keys mapped with my custom &lt;code&gt;CapsLock&lt;/code&gt; key using Linux &lt;code&gt;.Xkeymap&lt;/code&gt; configuration, e.g:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;D&lt;/code&gt;: Delete&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;F&lt;/code&gt;: Backspace&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;I&lt;/code&gt;, &lt;code&gt;J&lt;/code&gt;, &lt;code&gt;K&lt;/code&gt; and &lt;code&gt;L&lt;/code&gt;: arrow keys.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;U&lt;/code&gt;: Page up&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;O&lt;/code&gt;: Page down&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;H&lt;/code&gt;: Home&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Ö&lt;/code&gt;: End&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;… and it is pretty easy to hit &lt;code&gt;CapsLock&lt;/code&gt; with my left little finger and one of those keys with my right hand fingers (except &lt;code&gt;D&lt;/code&gt; and &lt;code&gt;F&lt;/code&gt; using left hand, of course). These key mappings work the same way if I use my laptop keyboard or Dygma Raise - which is a good thing: they can be in my muscle memory for any keyboard I use with my Ubuntu (in fact, I have these mappings in all my Ubuntu machines at home).&lt;/p&gt;

&lt;h3&gt;
  
  
  What Next?
&lt;/h3&gt;

&lt;p&gt;Who knows? Maybe I realize that there is a way to make my new Dygma Keyboard even more ergonomic with some new astounding realization how to configure it using the Bazecor.&lt;/p&gt;

&lt;p&gt;One thing that I have been thinking about with the split keyboard is to use a trackpad instead of the mouse - I could use my thumbs with a trackpad positioned between the keyboard halves now that I have space for it there - thanks to Dygma Raise split keyboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update 2020-10-03
&lt;/h3&gt;

&lt;p&gt;I have now used my brand new Dygma Raise about a week. I have forced myself to use the new key mappings I configured and I have done some fine-tuning. This chapter is an update to the original blog post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forcing oneself to use the new key mappings.&lt;/strong&gt; Learning the new key mappings will take some time. After one week I’m still stumbling to find the numbers in the new place in layer 1. The parenthesis I learned pretty quickly - probably because in programming one uses the parenthesis a lot more than numbers. I realized that one just have to force oneself to use the new key mappings even though it would be faster to use the old keys father away. But I believe that in a few weeks the new key mappings will go to the muscle memory and then it is a lot faster to use the new keys near the home row.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some tweaking with layer 0.&lt;/strong&gt; I did some modifications to layer 0 regarding the thumb buttons. The new key mapping is below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wRqPALxK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-09-28-dygma-raise-keyboard-reflections-part-1_img_5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wRqPALxK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-09-28-dygma-raise-keyboard-reflections-part-1_img_5.jpg" alt="New layer 0"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I changed the right lower thumb buttons as in the left side. I realized that it is easier to remember and use them this way. Now &lt;code&gt;Shift&lt;/code&gt; and &lt;code&gt;Ctrl&lt;/code&gt; keys are mirror images just like in their standard positions.&lt;/li&gt;
&lt;li&gt;I moved &lt;code&gt;Enter&lt;/code&gt; key next to &lt;code&gt;Space&lt;/code&gt; key. It is a bit awkward to twist the right hand thumb to this key but I sacrifice a bit in ergonomics to have the &lt;code&gt;Shift&lt;/code&gt; and &lt;code&gt;Ctrl&lt;/code&gt; keys in more logical positions.&lt;/li&gt;
&lt;li&gt;Then a bit later I realized that I can utilize the double action key functionality: I configured &lt;code&gt;Enter&lt;/code&gt; in the left side but with double action: when hit once it is &lt;code&gt;Enter&lt;/code&gt; but when hold down it is &lt;code&gt;Shift to layer 1&lt;/code&gt; as previous.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Some tweaking with layer 1.&lt;/strong&gt; I did some modifications to layer 1. The new key mapping is below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--13YSDFWS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-09-28-dygma-raise-keyboard-reflections-part-1_img_6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--13YSDFWS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-09-28-dygma-raise-keyboard-reflections-part-1_img_6.jpg" alt="New layer 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I moved the &lt;code&gt;Esc&lt;/code&gt; key below the home row. The reason was to have a more logical layout for the numbers.&lt;/li&gt;
&lt;li&gt;I realized that the keys below the right home row are empty and I can utilize that row. So:&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Tab&lt;/code&gt; added next to &lt;code&gt;Esc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;I moved &lt;code&gt;\&lt;/code&gt; key next to &lt;code&gt;tab&lt;/code&gt;. &lt;code&gt;\&lt;/code&gt; key provides the 

``&lt;code&gt;

character that I need in Slack to provide code snippets without formatting.
- The weird two dots key provides the precious tilde key (&lt;/code&gt;~&lt;code&gt;) that a programmer needs in terminal when referring to the user home directory. This key is in standard position really far a way in second upper row near &lt;/code&gt;Enter&lt;code&gt; key.
- &lt;/code&gt;'&lt;code&gt; key actually provides the asterisk (&lt;/code&gt;*&lt;code&gt;) when used with &lt;/code&gt;Shift&lt;code&gt;. The asterisk key is used in terminal to represent any number of characters - also used quite often. This key is in standard position next to the &lt;/code&gt;Enter&lt;code&gt; key and therefore a bit far away.
- I added &lt;/code&gt;Enter&lt;code&gt; also here in the second layer in the lower right thumb. After some experimentation I realized that it is actually easier to hit &lt;/code&gt;Shift to Layer 1&lt;code&gt; and &lt;/code&gt;Enter&lt;code&gt; in this new position than the &lt;/code&gt;Enter&lt;code&gt; key in layer 0 (I hit Enter quite often and I began to feel uncomfortable to twist my thumb to the new &lt;/code&gt;Enter&lt;code&gt; key next to &lt;/code&gt;Space&lt;code&gt; key). This is actually not important anymore when I realized to utilize the double action key (see previous layer). Well, maybe sometimes when adding a lot of numbers it might be easier to have the &lt;/code&gt;Enter&lt;code&gt; key also in this layer.
- I also added &lt;/code&gt;CapsLock&lt;code&gt; and &lt;/code&gt;Left Shift` keys to this layer since I’m planning to start learning to hit these keys as modifiers for the various parentheses (possibly, let’s see).
- I also added the mouse buttons in this layer since I realized that sometimes it is nice to paste something that I painted earlier using the mouse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cheat sheet.&lt;/strong&gt; I created a cheat sheet for myself to learn the new key mappings with Dygma Raise faster. On the left side note are the thumb buttons, on the right side note are layer 0 key mappings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9zopDuyr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-09-28-dygma-raise-keyboard-reflections-part-1_img_7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9zopDuyr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.karimarttila.fi/img/2020-09-28-dygma-raise-keyboard-reflections-part-1_img_7.jpg" alt="Cheat sheet"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ordered new switches.&lt;/strong&gt; I experimented with the test switches Dygma kindly added to the box. First I thought that I would order &lt;code&gt;Kailh Speed Silver&lt;/code&gt; switches (Linear, 40 cN, Pretravel 1.1 mm, Travel 3.5 mm) since I feel that the &lt;code&gt;Cherry MX Brown&lt;/code&gt; (Tactile, 55 cN, Pretravel 2 mm, Travel 4 mm) switches that I ordered with my Dygma are a bit too heavy and &lt;code&gt;Kailh Speed Silver&lt;/code&gt; switches were really easy to press. I also tried the &lt;code&gt;Kailh Speed Copper&lt;/code&gt; (tactile, 50 cN, Pretravel 1,1 mm, Travel 3,5 mm) - it was a nice switch also, a bit more consistent feeling that with &lt;code&gt;Cherry MX Brown&lt;/code&gt;. But then I surprised myself. I tried &lt;code&gt;Kailh Speed Bronze&lt;/code&gt; (clicky, 60 cN, Pretravel 1.1 mm, Travel 3.5 mm) - and I immediately fell in love with the feeling and the “click” which is really sharp and provides a really nice haptic and auditory feedback regarding a key press without being too heavy. I ordered &lt;code&gt;Kailh Speed Bronze&lt;/code&gt; switches and also &lt;code&gt;Kailh Low Profile Choc White&lt;/code&gt; switches for the lower thumb buttons.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;If you are a programmer and you are looking for a top-quality mechanical keyboard with absolute configurability - look no further: you want Dygma Raise. With Dygma Raise your imagination is your limit on how you can configure your new keyboard.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The writer is working at Metosin using Clojure in cloud projects. If you are interested to start a Clojure project in Finland or you are interested to get Clojure training in Finland you can contact me by sending email to my Metosin email address or contact me via LinkedIn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Kari Marttila&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kari Marttila’s Home Page in LinkedIn: &lt;a href="https://www.linkedin.com/in/karimarttila/"&gt;https://www.linkedin.com/in/karimarttila/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>keyboard</category>
      <category>productivity</category>
      <category>clojure</category>
    </item>
  </channel>
</rss>
