Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/TvkQWWs6)

# Features of modern Java

# Цели и задачи л/р:

На основе индивидуального задания произвести разработку бизнес-логики бэкэнда entriprise-системы.

В ходе реализации необходимо использовать возможности современных версий языка Java:

* Pattern matching для switch
* строковые шаблоны))))))))))))))
* расширенные возможности стандартной библиотеки Java
Expand All @@ -13,61 +17,81 @@
* и т.д.

# Обязательное условие:

* Использование системы сборки Gradle
* Код должен быть отлажен и протестирован

# Дедлайн 24.12.2025 23:59

# Задание
Бизнес-логика для системы управления проектами. Система позволяет группе разработчиков управлять разработкой программных проектов. В ней определены следующие объекты:
* Проект. У каждого проекта есть определенная команда разработчиков, тестировщиков и один менеджер. Также к проекту может быть привязан тимлидер. У проекта определены различные майлстоуны. К каждому проекту могут быть привязаны сообщения об ошибках.
* Майлстоун. Одна из итераций цикла разработки проекта. Привязан к определенным датам. К майлстоунам привязаны определенные тикеты (задания). Майлстоун имеет определенный статус: открыт, активен или закрыт. Майлстоун может быть закрыт только когда все его тикеты выполнены. В каждый момент времени у проекта может быть только один майлстоун.
* Тикет. Определенное задание для разработчиков. Может быть выдано определенной группе разработчиков. Привязан к определенному проекту и майлстоуну. Имеет статус: новый, принятый, в процессе выполнения, выполнен.
* Сообщение об ошибке. Отчет о найденной ошибке в проекте. Привязан к определенному проекту. Имеет статус: новый, исправленный, протестированный, закрытый.

Бизнес-логика для системы управления проектами. Система позволяет группе разработчиков управлять разработкой программных
проектов. В ней определены следующие объекты:

* Проект. У каждого проекта есть определенная команда разработчиков, тестировщиков и один менеджер. Также к проекту
может быть привязан тимлидер. У проекта определены различные майлстоуны. К каждому проекту могут быть привязаны
сообщения об ошибках.
* Майлстоун. Одна из итераций цикла разработки проекта. Привязан к определенным датам. К майлстоунам привязаны
определенные тикеты (задания). Майлстоун имеет определенный статус: открыт, активен или закрыт. Майлстоун может быть
закрыт только когда все его тикеты выполнены. В каждый момент времени у проекта может быть только один майлстоун.
* Тикет. Определенное задание для разработчиков. Может быть выдано определенной группе разработчиков. Привязан к
определенному проекту и майлстоуну. Имеет статус: новый, принятый, в процессе выполнения, выполнен.
* Сообщение об ошибке. Отчет о найденной ошибке в проекте. Привязан к определенному проекту. Имеет статус: новый,
исправленный, протестированный, закрытый.

В системе определены следующие роли для пользователей:

* менеджер;
* тимлидер;
* разработчик;
* тестировщик.
Для каждого проекта у пользователя определена своя роль (если он участвует в разработке данного проекта).

У всех пользователей системы есть возможность:

* зарегистрироваться;
* просмотреть все проекты в которых они участвуют;
* посмотреть список заданий, который был им выдан;
* посмотреть список отчетов об ошибках, которые ему надо исправить;
* создать новый проект.

Функции менеджера проекта:

* Управление пользователями:

1. назначение тимлидера
2. добавление разработчика к проекту
3. добавление тестировщика к проекту

* Управление майлстоунами:

1. создание нового майлстоуна
2. изменение статуса майлстоуна

* Управление тикетами

1. создание нового тикета
2. привязка разработчика к тикету
3. проверка выполнения тикета

Функции тимлидера:

* Управление тикетами

1. создание нового тикета
2. привязка разработчика к тикету
3. проверка выполнения тикета

* Выполнение тикетов

Функции разработчика:

* Выполнение тикетов
* Создание сообщений об ошибках
* Исправление сообщений об ошибках

Функции тестировщика:

* Тестирование проекта
* Создание сообщений об ошибках
* Проверка исправления сообщений об ошибках
* Проверка исправления сообщений об ошибках
15 changes: 15 additions & 0 deletions RESULT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# What was used

Фичи, которые были использованы

- java 16 [records](https://openjdk.org/jeps/395)
- java 21 [sequenced collections](https://openjdk.org/jeps/431)
- java 21 [virtual threads](https://openjdk.org/jeps/444) неявно в structured scope
- java 25 preview [structured concurrency](https://openjdk.org/jeps/505)
- java 17 [sealed classes](https://openjdk.org/jeps/409)
- java 21 [pattern matching for switch](https://openjdk.org/jeps/409)
- java 22 [unnamed variables](https://openjdk.org/jeps/456)
- java 24 [stream gatherers](https://openjdk.org/jeps/485)
- java 25 [Compact Source Files and Instance Main Methods](https://openjdk.org/jeps/512)
- java 23 [Markdown documentation comments](https://openjdk.org/jeps/467)
- etc
22 changes: 20 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ plugins {
group = "org.lab"
version = "1.0-SNAPSHOT"

java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
sourceCompatibility = JavaVersion.VERSION_25
targetCompatibility = JavaVersion.VERSION_25
}
}

repositories {
mavenCentral()
}
Expand All @@ -15,6 +23,16 @@ dependencies {
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.test {
tasks.withType<JavaCompile> {
options.compilerArgs.add("--enable-preview")
options.compilerArgs.add("-Xlint:preview")
}

tasks.withType<Test> {
useJUnitPlatform()
}
jvmArgs("--enable-preview")
}

tasks.withType<JavaExec> {
jvmArgs("--enable-preview")
}
123 changes: 120 additions & 3 deletions src/main/java/org/lab/Main.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,121 @@
void main() {
IO.println("Hello and welcome!");
}
package org.lab;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Gatherers;
import org.lab.domain.application.impl.DeveloperServiceImpl;
import org.lab.domain.application.impl.FindingUtilsService;
import org.lab.domain.application.impl.ManagerServiceImpl;
import org.lab.domain.application.impl.QualityGateServiceImpl;
import org.lab.domain.application.impl.TeamLeadServiceImpl;
import org.lab.domain.application.impl.TesterServiceImpl;
import org.lab.domain.application.impl.UserServiceImpl;
import org.lab.domain.model.enums.MilestoneStatus;
import org.lab.domain.model.user.User;
import org.lab.domain.port.ProjectRepositoryPort;
import org.lab.infrastructure.adapter.InMemoryMilestoneRepositoryAdapter;
import org.lab.infrastructure.adapter.InMemoryProjectRepositoryAdapter;
import org.lab.infrastructure.adapter.InMemoryUserRepositoryAdapter;
import org.lab.infrastructure.adapter.InMemoryWorkItemRepositoryAdapter;

public class Main {

/// Hello from **Markdown**!
///
/// - I can use *list*
/// - I can use `code`
///
/// ```java
/// IO.println("Hello from Java!");
///```
///
/// @throws IllegalStateException if the Universe has ended
void main() {
var userRepo = new InMemoryUserRepositoryAdapter();
var projectRepo = new InMemoryProjectRepositoryAdapter();
var milestoneRepo = new InMemoryMilestoneRepositoryAdapter();
var workItemRepo = new InMemoryWorkItemRepositoryAdapter();

var findingUtilsService = new FindingUtilsService(workItemRepo);
var qualityGateService = new QualityGateServiceImpl();

var userService = new UserServiceImpl(userRepo, projectRepo, workItemRepo);
var managerService = new ManagerServiceImpl(projectRepo, milestoneRepo, workItemRepo, qualityGateService, findingUtilsService);
var teamLeadService = new TeamLeadServiceImpl(projectRepo, milestoneRepo, workItemRepo, qualityGateService, findingUtilsService);
var developerService = new DeveloperServiceImpl(projectRepo, workItemRepo, findingUtilsService);
var testerService = new TesterServiceImpl(projectRepo, workItemRepo, findingUtilsService);

var alice = userService.registerUser("Alice Manager");
var bob = userService.registerUser("Bob Developer");
var charlie = userService.registerUser("Charlie Tester");
var dave = userService.registerUser("Dave TeamLead");

var project = userService.createProject(alice, "Super App 2024");

managerService.assignTeamLead(alice, project.id(), dave);
managerService.addDeveloper(alice, project.id(), bob);
managerService.addTester(alice, project.id(), charlie);

printTeam(projectRepo, project.id());

var milestone = managerService.createMilestone(alice, project.id(), "MVP Release");
managerService.updateMilestoneStatus(alice, milestone.id(), MilestoneStatus.ACTIVE);
System.out.printf("Milestone '%s' is now ACTIVE%n", milestone.name()); // printf is 'deliberately' not available in IO
IO.println("\n--- 3. Task Lifecycle (Ticket) ---");

IO.print("Creating ticket with Structured Concurrency... ");
var ticket = managerService.createTicket(alice, project.id(), "Implement Login OAuth", milestone.id());
IO.println("Done. Ticket ID: " + ticket.id());

ticket = managerService.assignTicket(alice, ticket.id(), bob);
IO.println("Assigned to: " + ticket.assignees().getFirst().name());

ticket = developerService.startProgress(bob, ticket.id());
IO.println("Bob starts working. Status: " + ticket.status());

ticket = developerService.resolveTicket(bob, ticket.id());
IO.println("Bob finished working. Status: " + ticket.status());

ticket = managerService.verifyTicket(alice, ticket.id(), true);
IO.println("Alice verified the ticket. Final Status: " + ticket.status());

IO.println("\n--- 4. Quality Assurance (Bug) ---");

var bug = testerService.createBug(charlie, project.id(), "Login Page 404", "Clicking login throws 404");
IO.println("Bug Reported by Charlie: " + bug.title());

bug = developerService.fixBug(bob, bug.id());
IO.println("Bob fixed the bug. Status: " + bug.status());

bug = testerService.verifyBugFix(charlie, bug.id(), true);
IO.println("Charlie verified the fix. Status: " + bug.status());
}

private static void printTeam(ProjectRepositoryPort repo, String projectId) {
var project = repo.findById(projectId).orElseThrow();
var members = project.getAllMembers();

var userRolesMap = members.stream().gather(Gatherers.<User, Map<String, Set<String>>>fold(
HashMap::new,
(acc, user) -> {
var role = project.getRoleFor(user);
acc.computeIfAbsent(user.id(), _ -> new HashSet<>()).add(role.toString());
return acc;
}
)
).findFirst().orElse(Map.of());

IO.println("Team created. Members:");
userRolesMap.forEach((userId, roles) -> {
String userName = members.stream()
.filter(u -> u.id().equals(userId))
.map(User::name)
.findFirst()
.orElse("Unknown");

IO.println(" - " + userName + " (" + userId + "): " + String.join(", ", roles));
});
}
}
16 changes: 16 additions & 0 deletions src/main/java/org/lab/domain/application/DeveloperService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.lab.domain.application;

import org.lab.domain.model.user.User;
import org.lab.domain.model.workitems.BugReport;
import org.lab.domain.model.workitems.Ticket;

public interface DeveloperService {
// execution
Ticket startProgress(User lead, String ticketId);

Ticket resolveTicket(User lead, String ticketId);

// bugs
BugReport reportBug(User developer, String projectId, String title, String description);
BugReport fixBug(User developer, String bugId);
}
28 changes: 28 additions & 0 deletions src/main/java/org/lab/domain/application/ManagerService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.lab.domain.application;

import org.lab.domain.model.enums.MilestoneStatus;
import org.lab.domain.model.user.User;
import org.lab.domain.model.workitems.Milestone;
import org.lab.domain.model.workitems.Project;
import org.lab.domain.model.workitems.Ticket;

public interface ManagerService {
// team management
Project assignTeamLead(User manager, String projectId, User newTeamLead);

Project addDeveloper(User manager, String projectId, User newDeveloper);

Project addTester(User manager, String projectId, User newTester);

// milestone management
Milestone createMilestone(User manager, String projectId, String name);

Milestone updateMilestoneStatus(User manager, String milestoneId, MilestoneStatus newStatus);

// ticket management
Ticket createTicket(User manager, String projectId, String title, String milestoneId);

Ticket assignTicket(User manager, String ticketId, User developer);

Ticket verifyTicket(User manager, String ticketId, boolean approved);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.lab.domain.application;

public interface QualityGateService {

record ComplianceResult(boolean passed, String message) {
}

ComplianceResult checkTicketReadyToClose(String ticketId);
}
18 changes: 18 additions & 0 deletions src/main/java/org/lab/domain/application/TeamLeadService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.lab.domain.application;

import org.lab.domain.model.user.User;
import org.lab.domain.model.workitems.Ticket;

public interface TeamLeadService {
// management
Ticket createTicket(User lead, String projectId, String title, String milestoneId);

Ticket assignTicket(User lead, String ticketId, User developer);

Ticket verifyTicket(User lead, String ticketId, boolean approved);

// execution
Ticket startProgress(User lead, String ticketId);

Ticket resolveTicket(User lead, String ticketId);
}
13 changes: 13 additions & 0 deletions src/main/java/org/lab/domain/application/TesterService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.lab.domain.application;

import java.util.List;
import org.lab.domain.model.user.User;
import org.lab.domain.model.workitems.BugReport;

public interface TesterService {
BugReport createBug(User tester, String projectId, String title, String description);

BugReport verifyBugFix(User tester, String bugId, boolean approved);

List<BugReport> getBugsReadyForVerification(User tester, String projectId);
}
19 changes: 19 additions & 0 deletions src/main/java/org/lab/domain/application/UserService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.lab.domain.application;

import java.util.List;
import org.lab.domain.model.user.User;
import org.lab.domain.model.workitems.BugReport;
import org.lab.domain.model.workitems.Project;
import org.lab.domain.model.workitems.Ticket;

public interface UserService {
User registerUser(String name);

Project createProject(User creator, String projectName);

List<Project> getMyProjects(User user);

List<Ticket> getMyAssignments(User user);

List<BugReport> getBugsToFix(User user);
}
Loading