1 package com.guinetik.hexafun.examples.tasks;
2
3 import static com.guinetik.hexafun.examples.tasks.TaskInputs.*;
4 import static com.guinetik.hexafun.examples.tasks.TaskUseCases.*;
5
6 import com.guinetik.hexafun.HexaApp;
7 import com.guinetik.hexafun.HexaFun;
8 import com.guinetik.hexafun.fun.Result;
9 import java.util.List;
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 public class TaskApp {
26
27 private final HexaApp app;
28
29
30
31
32 public TaskApp(TaskRepository repository) {
33 this.app = HexaFun.dsl()
34
35 .withPort(TaskRepository.class, repository)
36
37 .useCase(CREATE)
38 .validate(TaskValidators::validateCreateTitle)
39 .validate(TaskValidators::validateCreateTitleLength)
40 .handle(input -> {
41 Task task = Task.create(input.title(), input.description());
42 return Result.ok(repository.save(task));
43 })
44
45 .useCase(START)
46 .validate(TaskValidators::validateStartTaskId)
47 .handle(input ->
48 repository
49 .findById(input.taskId())
50 .map(task -> Result.ok(repository.save(task.start())))
51 .orElse(Result.fail("Task not found: " + input.taskId()))
52 )
53
54 .useCase(COMPLETE)
55 .validate(TaskValidators::validateCompleteTaskId)
56 .handle(input ->
57 repository
58 .findById(input.taskId())
59 .map(task -> Result.ok(repository.save(task.complete())))
60 .orElse(Result.fail("Task not found: " + input.taskId()))
61 )
62
63 .useCase(UPDATE)
64 .validate(TaskValidators::validateUpdateTaskId)
65 .validate(TaskValidators::validateUpdateTitle)
66 .handle(input ->
67 repository
68 .findById(input.taskId())
69 .map(task -> {
70 Task updated = task
71 .withTitle(input.title())
72 .withDescription(input.description());
73 return Result.ok(repository.save(updated));
74 })
75 .orElse(Result.fail("Task not found: " + input.taskId()))
76 )
77
78 .useCase(DELETE)
79 .validate(TaskValidators::validateDeleteTaskId)
80 .handle(input -> Result.ok(repository.delete(input.taskId())))
81
82 .useCase(FIND)
83 .validate(TaskValidators::validateFindTaskId)
84 .handle(input ->
85 repository
86 .findById(input.taskId())
87 .map(Result::ok)
88 .orElse(Result.fail("Task not found: " + input.taskId()))
89 )
90
91 .useCase(LIST)
92 .handle(input -> repository.findAll())
93 .build();
94 }
95
96
97
98
99 public static TaskApp withInMemoryRepo() {
100 return new TaskApp(new InMemoryTaskRepository());
101 }
102
103
104
105 public Result<Task> createTask(String title, String description) {
106 return app.invoke(CREATE, new CreateTask(title, description));
107 }
108
109 public Result<Task> startTask(String taskId) {
110 return app.invoke(START, new StartTask(taskId));
111 }
112
113 public Result<Task> completeTask(String taskId) {
114 return app.invoke(COMPLETE, new CompleteTask(taskId));
115 }
116
117 public Result<Task> updateTask(
118 String taskId,
119 String title,
120 String description
121 ) {
122 return app.invoke(UPDATE, new UpdateTask(taskId, title, description));
123 }
124
125 public Result<Boolean> deleteTask(String taskId) {
126 return app.invoke(DELETE, new DeleteTask(taskId));
127 }
128
129 public Result<Task> findTask(String taskId) {
130 return app.invoke(FIND, new FindTask(taskId));
131 }
132
133 public List<Task> listTasks() {
134 return app.invoke(LIST, null);
135 }
136
137
138
139
140 public HexaApp getApp() {
141 return app;
142 }
143
144
145
146
147 public TaskRepository getRepository() {
148 return app.port(TaskRepository.class);
149 }
150
151
152
153 public static void main(String[] args) {
154 System.out.println("=== TaskApp Demo ===\n");
155
156
157 TaskApp taskApp = TaskApp.withInMemoryRepo();
158
159
160 System.out.println(
161 "Registered ports: " + taskApp.getApp().registeredPorts()
162 );
163 System.out.println(
164 "Has TaskRepository: " +
165 taskApp.getApp().hasPort(TaskRepository.class)
166 );
167 System.out.println();
168
169
170 System.out.println("Creating tasks...");
171 Result<Task> task1 = taskApp.createTask(
172 "Learn HexaFun",
173 "Study the fluent DSL"
174 );
175 Result<Task> task2 = taskApp.createTask(
176 "Write tests",
177 "Add comprehensive tests"
178 );
179 Result<Task> task3 = taskApp.createTask(
180 "Deploy app",
181 "Push to production"
182 );
183
184 System.out.println("Created: " + task1.get().title());
185 System.out.println("Created: " + task2.get().title());
186 System.out.println("Created: " + task3.get().title());
187 System.out.println();
188
189
190 System.out.println("All tasks:");
191 taskApp
192 .listTasks()
193 .forEach(t ->
194 System.out.println(
195 " - [" + (t.completed() ? "X" : " ") + "] " + t.title()
196 )
197 );
198 System.out.println();
199
200
201 String taskId = task1.get().id();
202 System.out.println("Completing task: " + task1.get().title());
203 taskApp.completeTask(taskId);
204 System.out.println();
205
206
207 System.out.println("Tasks after completion:");
208 taskApp
209 .listTasks()
210 .forEach(t ->
211 System.out.println(
212 " - [" + (t.completed() ? "X" : " ") + "] " + t.title()
213 )
214 );
215 System.out.println();
216
217
218 System.out.println("Trying to create task with empty title...");
219 Result<Task> invalid = taskApp.createTask("", "No title");
220 System.out.println(
221 "Result: " +
222 (invalid.isSuccess() ? "OK" : "FAILED: " + invalid.error())
223 );
224 System.out.println();
225
226
227 System.out.println("Trying to create task with very long title...");
228 String longTitle = "A".repeat(101);
229 Result<Task> tooLong = taskApp.createTask(longTitle, "Too long");
230 System.out.println(
231 "Result: " +
232 (tooLong.isSuccess() ? "OK" : "FAILED: " + tooLong.error())
233 );
234 System.out.println();
235
236
237 System.out.println("Finding task by ID: " + taskId);
238 Result<Task> found = taskApp.findTask(taskId);
239 System.out.println(
240 "Found: " +
241 found.get().title() +
242 " (completed: " +
243 found.get().completed() +
244 ")"
245 );
246 System.out.println();
247
248
249 System.out.println("Deleting task: " + task3.get().title());
250 taskApp.deleteTask(task3.get().id());
251 System.out.println("Tasks remaining: " + taskApp.listTasks().size());
252 }
253 }