Skip to content

Projects and Events

BrunoRosendo edited this page Sep 11, 2023 · 4 revisions

Just as expected, projects and events share many similarities, such as title, description, photo, team, and more. Consequently, we often employ the concept of an Activity to abstract these similarities. It's important to note that this generalization is implemented somewhat differently in each layer (except for the controller), so we'll delve into the specifics in the following section.

Contents

Models

Implementing the models closely resembles Kotlin's inheritance, but there are some noteworthy details:

  • Inheritance is configured as JOINED to associate each subclass with its dedicated table. However, there may be cases where a different mapping strategy is preferred.
  • The @Entity annotation is retained in the Activity class since it can also be used as an independent entity in certain scenarios.
  • Fields within the Activity class must be declared as open. Failure to do so may result in the error: Getter methods of lazy classes cannot be final.
  • While fields from the Activity class should still be included in the constructors of subclasses, validation logic should be concentrated within the Activity class. This ensures the consistency of errors in the subclasses.
// Incomplete
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
abstract class Activity(
    @JsonProperty(required = true)
    @field:Size(min = Constants.Title.minSize, max = Constants.Title.maxSize)
    open var title: String,

    @Id
    @GeneratedValue
    open val id: Long? = null
)
// Incomplete
@Entity
class Project(
    title: String,

    var isArchived: Boolean = false,

    id: Long? = null
) : Activity(title, id)

Repositories

Generalization in the repositories follows a straightforward approach. We declare an interface that uses an Activity subclass as a generic parameter:

@Repository
interface ActivityRepository<T : Activity> : CrudRepository<T, Long> {
    fun findBySlug(slug: String?): T?
}
@Repository
interface EventRepository : ActivityRepository<Event> {
    fun findAllByCategory(category: String): List<Event>
}

DTOs

Just like the models and repositories, DTOs use the activity abstraction to prevent code redundancy in the service layer. To achieve this, we establish an abstract ActivityDto with an Activity subclass as a generic parameter:

abstract class ActivityDto<T : Activity>(
    val title: String,
    val description: String,
    val teamMembersIds: List<Long>?,
    val slug: String?,
    var image: String?,
    @JsonIgnore
    var imageFile: MultipartFile? = null
) : EntityDto<T>()
class EventDto(
    title: String,
    description: String,
    teamMembersIds: List<Long>?,
    slug: String?,
    image: String?,

    val registerUrl: String?,
    val dateInterval: DateInterval,
    val location: String?,
    val category: String?
) : ActivityDto<Event>(title, description, teamMembersIds, slug, image)

Services

In the services layer, the benefits of the activity abstraction become evident. Here, we create an abstract class that encapsulates common operations shared between projects and events, while specific operations reside within their respective services.

An important point to highlight is that the abstract service is named AbstractActivityService. This distinction is crucial because there is also an ActivityService utilized by services that exclusively deal with activities and do not differentiate between projects and events.

The structure resembles something like this:

// Incomplete
@Service
abstract class AbstractActivityService<T : Activity>(
    protected val repository: ActivityRepository<T>,
    protected val accountService: AccountService,
    protected val fileUploader: FileUploader
) {
    ...
}
@Service
class ActivityService(
    repository: ActivityRepository<Activity>,
    accountService: AccountService,
    fileUploader: FileUploader
) : AbstractActivityService<Activity>(repository, accountService, fileUploader)
// Incomplete
@Service
class EventService(
    override val repository: EventRepository,
    accountService: AccountService,
    fileUploader: FileUploader
) : AbstractActivityService<Event>(repository, accountService, fileUploader) {
    ...
}