patternMinor
A very simple TODO app's domain model
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:
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());
}
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.
Of course, it's a bit sloppy, so let's specify it properly. I'm using clojure.spec for this.
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.
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.
and some basic operations
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.