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
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ fun Requested.toApiRequested(): ApiRequested = when (this) {
is FuncUpdateRequested -> ApiFuncUpdateRequested(requestId, requestStatus, id)
is NamespaceAppendRequested -> ApiNamespaceAppendRequested(requestId, requestStatus, id, workspaceId)
is NamespaceUpdateRequested -> ApiNamespaceUpdateRequested(requestId, requestStatus, id)
is NamespaceDeleteRequested -> ApiNamespaceDeleteRequested(requestId, requestStatus, id)
is StateSetRequested -> ApiStateSetRequested(requestId, requestStatus)
is TopicAppendEventRequested -> ApiTopicAppendRequested(requestId, requestStatus, id)
is TopicCreateRequested -> ApiTopicCreateRequested(requestId, requestStatus, id, workspaceId, namespaceId, type)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.hamal.api.http.controller.namespace

import io.hamal.api.http.controller.accepted
import io.hamal.core.adapter.namespace.NamespaceDeletePort
import io.hamal.core.component.Retry
import io.hamal.lib.domain.vo.NamespaceId
import io.hamal.lib.sdk.api.ApiRequested
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController

@RestController
internal class NamespaceDeleteController(
private val retry: Retry,
private val deleteNamespace: NamespaceDeletePort
) {
@DeleteMapping("/v1/namespaces/{namespaceId}")
fun delete(
@PathVariable("namespaceId") namespaceId: NamespaceId,
): ResponseEntity<ApiRequested> = retry {
deleteNamespace(namespaceId).accepted()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package io.hamal.core.request.handler.namespace

import io.hamal.core.request.handler.BaseRequestHandlerTest
import io.hamal.lib.common.domain.CmdId.Companion.CmdId
import io.hamal.lib.common.domain.Limit
import io.hamal.lib.domain._enum.RequestStatuses.Submitted
import io.hamal.lib.domain.request.NamespaceAppendRequested
import io.hamal.lib.domain.request.NamespaceDeleteRequested
import io.hamal.lib.domain.vo.AuthId.Companion.AuthId
import io.hamal.lib.domain.vo.NamespaceFeatures
import io.hamal.lib.domain.vo.NamespaceId
import io.hamal.lib.domain.vo.NamespaceId.Companion.NamespaceId
import io.hamal.lib.domain.vo.NamespaceName.Companion.NamespaceName
import io.hamal.lib.domain.vo.NamespaceTreeId
import io.hamal.lib.domain.vo.RequestId.Companion.RequestId
import io.hamal.lib.domain.vo.RequestStatus.Companion.RequestStatus
import io.hamal.repository.api.NamespaceCmdRepository
import io.hamal.repository.api.NamespaceQueryRepository
import io.hamal.repository.api.NamespaceTreeCmdRepository
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.hasSize
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired


internal class DeleteHandlerTest : BaseRequestHandlerTest() {

@BeforeEach
fun beforeEach() {
namespaceCmdRepository.clear()
namespaceTreeRepository.clear()

namespaceCmdRepository.create(
NamespaceCmdRepository.CreateCmd(
id = CmdId(2),
namespaceId = NamespaceId.root,
name = NamespaceName("root"),
workspaceId = testWorkspace.id,
features = NamespaceFeatures.default
)
)
namespaceTreeRepository.create(
NamespaceTreeCmdRepository.CreateCmd(
id = CmdId(2),
treeId = NamespaceTreeId.root,
rootNodeId = NamespaceId.root,
workspaceId = testWorkspace.id
)
)
}

@Test
fun `Deletes namespace`() {
testAppendHandler(requestedAppendNamespace)
testDeleteHandler(requestedDeleteNamespace)

val namespaces = namespaceQueryRepository.list(
NamespaceQueryRepository.NamespaceQuery(
limit = Limit.all,
workspaceIds = listOf(testWorkspace.id)
)
)

assertThat(namespaces, hasSize(1))
assertThat(namespaceQueryRepository.find(NamespaceId(5)), Matchers.nullValue())
with(namespaces[0]) {
assertThat(namespaceId, equalTo(NamespaceId.root))
assertThat(name, equalTo(NamespaceName("root")))
assertThat(features, equalTo(NamespaceFeatures.default))
}
}

@Autowired
private lateinit var testAppendHandler: NamespaceAppendHandler

@Autowired
private lateinit var testDeleteHandler: NamespaceDeleteHandler

private val requestedDeleteNamespace by lazy {
NamespaceDeleteRequested(
requestId = RequestId(4),
requestedBy = AuthId(4),
requestStatus = RequestStatus(Submitted),
id = NamespaceId(5),
)
}

private val requestedAppendNamespace by lazy {
NamespaceAppendRequested(
requestId = RequestId(3),
requestedBy = AuthId(4),
requestStatus = RequestStatus(Submitted),
parentId = NamespaceId.root,
id = NamespaceId(5),
workspaceId = testWorkspace.id,
name = NamespaceName("awesome-namespace"),
features = null
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.hamal.core.adapter.namespace

import io.hamal.core.adapter.request.RequestEnqueuePort
import io.hamal.core.security.SecurityContext
import io.hamal.lib.domain.GenerateDomainId
import io.hamal.lib.domain._enum.RequestStatuses.Submitted
import io.hamal.lib.domain.request.NamespaceDeleteRequested
import io.hamal.lib.domain.vo.NamespaceId
import io.hamal.lib.domain.vo.RequestId
import io.hamal.lib.domain.vo.RequestStatus.Companion.RequestStatus
import io.hamal.repository.api.NamespaceTreeQueryRepository
import org.springframework.stereotype.Component

fun interface NamespaceDeletePort {
operator fun invoke(namespaceId: NamespaceId): NamespaceDeleteRequested
}

@Component
class NamespaceDeleteAdapter(
private val generateDomainId: GenerateDomainId,
private val requestEnqueue: RequestEnqueuePort,
private val namespaceTreeQueryRepository: NamespaceTreeQueryRepository,
private val namespaceGetPort: NamespaceGetPort
) : NamespaceDeletePort {
override fun invoke(namespaceId: NamespaceId): NamespaceDeleteRequested {
namespaceGetPort(namespaceId)

val root = namespaceTreeQueryRepository.get(namespaceId).root
if (root.value == namespaceId) {
throw IllegalArgumentException("Tried to delete root namespace")
}

return NamespaceDeleteRequested(
requestId = generateDomainId(::RequestId),
requestedBy = SecurityContext.currentAuthId,
requestStatus = RequestStatus(Submitted),
id = namespaceId,
).also(requestEnqueue::invoke)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.hamal.core.event.handler.namespace

import io.hamal.core.adapter.namespace.NamespaceDeletePort
import io.hamal.core.adapter.namespace_tree.NamespaceTreeGetSubTreePort
import io.hamal.core.event.InternalEventHandler
import io.hamal.lib.common.domain.CmdId
import io.hamal.repository.api.event.NamespaceDeletedEvent
import org.springframework.stereotype.Component

@Component
internal class NamespaceDeletedHandler(
private val getSubTree: NamespaceTreeGetSubTreePort,
private val deleteNamespace: NamespaceDeletePort,
) : InternalEventHandler<NamespaceDeletedEvent> {

override fun handle(cmdId: CmdId, evt: NamespaceDeletedEvent) {
val subTree = getSubTree(evt.namespace.id)

if (subTree.values.isNotEmpty()) {
subTree.values.forEach {
deleteNamespace(it)
}
}
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.hamal.core.request.handler.namespace

import io.hamal.core.event.InternalEventEmitter
import io.hamal.core.request.RequestHandler
import io.hamal.core.request.handler.cmdId
import io.hamal.lib.common.domain.CmdId
import io.hamal.lib.domain.request.NamespaceDeleteRequested
import io.hamal.repository.api.Namespace
import io.hamal.repository.api.NamespaceCmdRepository
import io.hamal.repository.api.event.NamespaceDeletedEvent
import org.springframework.stereotype.Component

@Component
class NamespaceDeleteHandler(
private val namespaceCmdRepository: NamespaceCmdRepository,
private val eventEmitter: InternalEventEmitter
) : RequestHandler<NamespaceDeleteRequested>(NamespaceDeleteRequested::class) {
override fun invoke(req: NamespaceDeleteRequested) {
deleteNamespace(req)
.also { emitEvent(req.cmdId(), it) }
}

private fun deleteNamespace(req: NamespaceDeleteRequested): Namespace {
return namespaceCmdRepository.delete(
req.id, NamespaceCmdRepository.DeleteCmd(
id = req.cmdId(),
namespaceId = req.id
)
)
}

private fun emitEvent(cmdId: CmdId, namespace: Namespace) {
eventEmitter.emit(cmdId, NamespaceDeletedEvent(namespace))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ interface NamespaceCmdRepository : CmdRepository {

fun update(namespaceId: NamespaceId, cmd: UpdateCmd): Namespace

fun delete(namespaceId: NamespaceId, cmd: DeleteCmd): Namespace

data class CreateCmd(
val id: CmdId,
val namespaceId: NamespaceId,
Expand All @@ -41,6 +43,11 @@ interface NamespaceCmdRepository : CmdRepository {
val name: NamespaceName? = null,
val features: NamespaceFeatures? = null
)

data class DeleteCmd(
val id: CmdId,
val namespaceId: NamespaceId,
)
}

interface NamespaceQueryRepository {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ val internalEventClasses = listOf(
FeedbackCreatedEvent::class,
NamespaceAppendedEvent::class,
NamespaceUpdatedEvent::class,
NamespaceDeletedEvent::class,
FuncCreatedEvent::class,
FuncUpdatedEvent::class,
FuncDeployedEvent::class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ data class NamespaceUpdatedEvent(
val namespace: Namespace,
) : InternalEvent()

data class NamespaceDeletedEvent(
val namespace: Namespace,
) : InternalEvent()
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import io.hamal.repository.memory.record.ProjectionMemory

internal class ProjectionCurrent : ProjectionMemory.BaseImpl<NamespaceId, Namespace>() {

fun delete(obj: Namespace) {
projection.remove(obj.id)
}

fun find(namespaceId: NamespaceId): Namespace? = projection[namespaceId]

fun find(namespaceName: NamespaceName): Namespace? = projection.values.find { it.name == namespaceName }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ package io.hamal.repository.memory.record.namespace
import io.hamal.lib.common.domain.Count
import io.hamal.lib.domain.vo.NamespaceId
import io.hamal.repository.api.Namespace
import io.hamal.repository.api.NamespaceCmdRepository.CreateCmd
import io.hamal.repository.api.NamespaceCmdRepository.UpdateCmd
import io.hamal.repository.api.NamespaceCmdRepository.*
import io.hamal.repository.api.NamespaceQueryRepository.NamespaceQuery
import io.hamal.repository.api.NamespaceRepository
import io.hamal.repository.memory.record.RecordMemoryRepository
Expand Down Expand Up @@ -58,6 +57,22 @@ class NamespaceMemoryRepository : RecordMemoryRepository<NamespaceId, NamespaceR
}
}

override fun delete(namespaceId: NamespaceId, cmd: DeleteCmd): Namespace {
return lock.withLock {
if (commandAlreadyApplied(cmd.id, namespaceId)) {
versionOf(namespaceId, cmd.id)
} else {
store(
NamespaceRecord.Deleted(
entityId = namespaceId,
cmdId = cmd.id,
)
)
(currentVersion(namespaceId)).also(currentProjection::delete)
}
}
}

override fun find(namespaceId: NamespaceId): Namespace? = lock.withLock { currentProjection.find(namespaceId) }

override fun list(query: NamespaceQuery): List<Namespace> = lock.withLock { currentProjection.list(query) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,4 @@ internal class ProjectionCurrent : ProjectionMemory.BaseImpl<NamespaceTreeId, Na
}

private val namespaceMapping = mutableMapOf<NamespaceId, NamespaceTreeId>()

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import io.hamal.lib.common.domain.Count
import io.hamal.lib.domain.vo.NamespaceId
import io.hamal.lib.domain.vo.NamespaceTreeId
import io.hamal.repository.api.NamespaceTree
import io.hamal.repository.api.NamespaceTreeCmdRepository
import io.hamal.repository.api.NamespaceTreeCmdRepository.AppendCmd
import io.hamal.repository.api.NamespaceTreeCmdRepository.CreateCmd
import io.hamal.repository.api.NamespaceTreeQueryRepository.NamespaceTreeQuery
import io.hamal.repository.api.NamespaceTreeRepository
import io.hamal.repository.memory.record.RecordMemoryRepository
Expand All @@ -19,7 +20,7 @@ class NamespaceTreeMemoryRepository : RecordMemoryRepository<NamespaceTreeId, Na
projections = listOf(ProjectionCurrent())
), NamespaceTreeRepository {

override fun create(cmd: NamespaceTreeCmdRepository.CreateCmd): NamespaceTree {
override fun create(cmd: CreateCmd): NamespaceTree {
return lock.withLock {
val treeId = cmd.treeId
if (commandAlreadyApplied(cmd.id, treeId)) {
Expand All @@ -38,7 +39,7 @@ class NamespaceTreeMemoryRepository : RecordMemoryRepository<NamespaceTreeId, Na
}
}

override fun append(cmd: NamespaceTreeCmdRepository.AppendCmd): NamespaceTree {
override fun append(cmd: AppendCmd): NamespaceTree {
return lock.withLock {
val treeId = cmd.treeId
if (commandAlreadyApplied(cmd.id, treeId)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ data class NamespaceEntity(
features = rec.features,
recordedAt = rec.recordedAt()
)

is NamespaceRecord.Deleted -> copy(
id = rec.entityId,
cmdId = rec.cmdId,
sequence = rec.sequence(),
recordedAt = rec.recordedAt()
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ sealed class NamespaceRecord(
internal object Adapter : RecordAdapter<NamespaceRecord>(
listOf(
Created::class,
Updated::class
Updated::class,
Deleted::class
)
)

Expand All @@ -37,5 +38,10 @@ sealed class NamespaceRecord(
val name: NamespaceName,
val features: NamespaceFeatures
) : NamespaceRecord()

data class Deleted(
override val entityId: NamespaceId,
override val cmdId: CmdId
) : NamespaceRecord()
}

Loading