     (throw (IllegalArgumentException. "No role specified to update")))
   (validate-role-exists circle role-name))
 
-(defn rename-role
+(defmacro updatefn [name docstring args & body]
+  `(defn ~name ~docstring ~args
+     (validate-updates ~(first args) ~(second args))
+     ~@body))
+
+(updatefn rename-role
   "Rename a role in the given circle."
   [circle role-name new-name]
-  (validate-updates circle role-name)
   (update-in circle [:roles]
              s/rename-keys {role-name new-name}))
 
-(defn update-role-purpose
+(updatefn update-role-purpose
   "Update the purpose of a role in the given circle."
   [circle role-name new-purpose]
-  (validate-updates circle role-name)
   (if (empty? new-purpose)
     (update-in circle [:roles role-name]
                dissoc :purpose)

(defn- get-domains [circle role-name]
  (get-in circle [:roles role-name :domains]))

(defn- validate-domain-not-present [circle role-name domain]
  (if (contains? (get-domains circle role-name) domain)
    (throw (IllegalArgumentException.
             (format "Domain '%s' already exists on role '%s'" domain
                     role-name)))))

(defn add-domain
  "Add a domain to a role in the given circle."
  [circle role-name domain]
  (validate-updates circle role-name)
  (validate-domain-not-present circle role-name domain)

  (let [domains (get-domains circle role-name)
        circle (if domains

                 (update-in circle [:roles role-name] assoc :domains #{}))]
    (update-in circle [:roles role-name :domains] conj domain)))

(updatefn remove-domain
  "Remove a domain from a role in the given circle."
  [circle role-name domain]
  (validate-domain (comp not contains?)
                   "Domain '%s' doesn't exist on role '%s'"
                   circle role-name domain)
  (let [result (update-in circle [:roles role-name :domains] disj domain)]
    (if (empty? (get-domains result role-name))
      (update-in result [:roles role-name] dissoc :domains)
