HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

A very simple TODO app's domain model

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
simpleappmodelverytododomain

Problem

I am currently rewriting a todo app from java to clojure. This is my first "real" clojure project so I'm not sure that what I write is idiomatic clojure code. I have just finished rewriting the domain module and I'd like to know how can I improve on it to have something which is idiomatic.

I had some simple classes in my original java project:

  • Box



  • BoxBlurbsProvider



  • EisenhowerMatrix



  • Importance



  • Urgency



  • Task



My original classes are these (note that I omitted documentation from my code):

```
@Getter
@Builder
public class Box {

private final Urgency urgency;
private final Importance importance;
private final String name;
private final String description;
private final Set tasks = new LinkedHashSet<>();

public void addTask(final Task task) {
tasks.add(task);
}

public void removeTask(final Task task) {
tasks.remove(task);
}
}

public interface BoxBlurbsProvider {
String getNameFor(Urgency urgency, Importance importance);
String getDescriptionFor(Urgency urgency, Importance importance);
}

public class EisenhowerMatrix {

@Value(staticConstructor = "of")
private static class BoxKey {
private final Urgency urgency;
private final Importance importance;
}

private final Map boxes = new LinkedHashMap<>();

@Getter
private final String name;

private EisenhowerMatrix(String name, BoxBlurbsProvider boxBlurbsProvider) {
this.name = name;
for (Importance importance : Importance.values()) {
for (Urgency urgency : Urgency.values()) {
boxes.put(BoxKey.of(urgency, importance), Box.builder()
.importance(importance)
.urgency(urgency)
.name(boxBlurbsProvider.getNameFor(urgency, importance))
.description(boxBlurbsProvider.getDescriptionFor(urgency, importance))
.build());
}

Solution

I started to write the java example as one-to-one mapping to clojure, but shortly realized that it's not entirely possible. The main reason that the original design is not sound for me.

So I was starting to think about the core concept and that can be summed up in one domain entity: the task.

Let's create a task.

(defn create-task
  [name desc urgent? important?]
  {:name name, :description desc, :urgent? urgent?, :important? important?})


Of course, it's a bit sloppy, so let's specify it properly. I'm using clojure.spec for this.

(s/def ::name (s/and string?
                     (comp not empty?)))
(s/def ::description string?)
(s/def ::urgent? boolean?)
(s/def ::important? boolean?)

(s/def ::task (s/keys :req-un [::name ::description ::urgent? ::important?]))
And let's specify the constructor function itself.

(s/fdef create-task
        :args (s/cat :name ::name, :description ::description, :urgent? ::urgent?, :important? ::important?)
        :ret ::task)


The box concept is not necessarily need any implementation. We are interested in the grouped tasks by boxes and this is what the group-by function is for.

(defn box-tasks
  [tasks]
  (group-by (fn [{:keys [urgent? important?]}] [urgent? important?]) tasks))

(s/def ::tasks (s/coll-of ::task))
(s/def ::box-key (s/tuple ::urgent? ::important?))
(s/def ::box-tasks (s/map-of ::box-key ::tasks))

(s/fdef box-tasks
        :args (s/cat :tasks ::tasks)
        :ret ::box-tasks)


Side note: the ::box-tasks definition is odd, the default generator will generate inconsistent box-keys and tasks for it.

If you really need a matrix, you can define a simple constructor function for it.

(defn create-matrix
  [name]
  {:name name, :tasks #{}})

(s/def ::matrix (s/keys :req-un [::name ::tasks]))

(s/fdef create-matrix
        :args (s/cat :name ::name)
        :ret ::matrix)


and some basic operations

(defn add-task    [m t] (update m :tasks conj t))
(defn remove-task [m t] (update m :tasks disj t))

(s/fdef add-task
        :args (s/cat :matrix ::matrix :task ::task)
        :ret ::matrix)

(s/fdef remove-task
        :args (s/cat :matrix ::matrix :task ::task)
        :ret ::matrix)

Code Snippets

(defn create-task
  [name desc urgent? important?]
  {:name name, :description desc, :urgent? urgent?, :important? important?})
(s/def ::name (s/and string?
                     (comp not empty?)))
(s/def ::description string?)
(s/def ::urgent? boolean?)
(s/def ::important? boolean?)

(s/def ::task (s/keys :req-un [::name ::description ::urgent? ::important?]))
And let's specify the constructor function itself.

(s/fdef create-task
        :args (s/cat :name ::name, :description ::description, :urgent? ::urgent?, :important? ::important?)
        :ret ::task)
(defn box-tasks
  [tasks]
  (group-by (fn [{:keys [urgent? important?]}] [urgent? important?]) tasks))

(s/def ::tasks (s/coll-of ::task))
(s/def ::box-key (s/tuple ::urgent? ::important?))
(s/def ::box-tasks (s/map-of ::box-key ::tasks))

(s/fdef box-tasks
        :args (s/cat :tasks ::tasks)
        :ret ::box-tasks)
(defn create-matrix
  [name]
  {:name name, :tasks #{}})

(s/def ::matrix (s/keys :req-un [::name ::tasks]))

(s/fdef create-matrix
        :args (s/cat :name ::name)
        :ret ::matrix)
(defn add-task    [m t] (update m :tasks conj t))
(defn remove-task [m t] (update m :tasks disj t))

(s/fdef add-task
        :args (s/cat :matrix ::matrix :task ::task)
        :ret ::matrix)

(s/fdef remove-task
        :args (s/cat :matrix ::matrix :task ::task)
        :ret ::matrix)

Context

StackExchange Code Review Q#141848, answer score: 3

Revisions (0)

No revisions yet.