안녕하세요.
저는 지금까지 7-8개의 사이드프로젝트를 해왔었는데요. 사이드를 할 때마다 모종의 이유로 동기를 잃고 흐지부지 되어서 그만하게 되더라고요.
이렇게 계속해서 사이드를 그만두다 보니까, 사이드 프로젝트는 단순히 단기간에 기술이나 가설을 실험하는 것 이상으로 갈 수 없고, 빠르게 끝나는게 당연한것이라고 생각하고 있었습니다.
그런데, 최근에 하고 있는 깃 애니몰즈 라는 프로젝트는 7개월 가량 운영을 하고 있는데도 동기도 잃지 않으면서, 할수록 재밌어지는 프로젝트이더라고요?
어떻게 해서 사이드 프로젝트가 이렇게 될 수 있을까 고민을 하다가 얻은 나름의 인사이트를 회고해보려고 합니다.
동기
사이드 프로젝트를 접게 되는 가장 큰 이유 중 하나는 구성원들이 동기를 잃기 때문이라고 생각해요.
저 같은 경우 프로젝트를 시작하는 초반에는 아이디어가 재미있거나, 새로운 기술을 사용해보고 싶거나 등의 이유로 프로젝트를 하는 것 자체가 동기가 되었던 것 같아요.
하지만, 시간이 조금 흐르고 원하는 바를 모두 달성하거나 하지 못한다는 생각이 든 후 에는 프로젝트를 하는 것 자체가 동기가 되지 않는 경우가 많더라고요.
사이드 프로젝트는 회사일과 달리 '돈'과 같은 고정적인 보상을 받지 못하다 보니 동기를 잃은 후에는 그만두기가 쉬운 것 같았습니다.
그렇다면, 이런 문제를 어떻게 해결할 수 있을까요?
1. 보상과 목표
사이드 프로젝트를 운영하는 입장에서, 첫 번째는 팀 구성원의 동기와 프로젝트의 목표를 잘 조율할 수 있어야 한다고 생각해요.
이때, 단순히 기술적으로 뛰어난 사람인지만 보는 게 아니라, 아래와 같은 관점에서 역으로 이 프로젝트가 구성원에게는 무엇을 해줄 수 있는지 생각을 하는 게 중요한 것 같습니다.
1. 프로젝트의 목표가 구성원들의 목표를 같이 만족시켜 줄 수 있는지, 언제까지 만족시켜줄 수 있을지?
2. 구성원들의 목표가 프로젝트의 방향성에 반하지 않는지?
하지만, 사이드 프로젝트이다 보니 2번은 어느 정도 희생할 수 있고 1번이 더 중요하다고 생각합니다. 예를 들어, 깃 애니몰즈 팀에 합류하는 개발자들은 모두 공통적으로 아래와 같은 목표에 동기부여를 받는 사람들이었습니다.
1. 프로젝트가 재미있어서 함께 만들고 싶다.
2. 회사에서는 하기 힘든 기술을 사용하면서 기술적으로 검증과 성장을 하고 싶다. (오버엔지니어링)
여기서 2번 같은 경우는 잘못하면 오버엔지니어링으로 이어져 프로젝트의 속도를 저하시키는 문제로 발전될 수도 있는데요. 그렇다면 사이드 프로젝트에서 오버엔지니어링이 정말 나쁜 것인가 생각을 해봤을 때, 저는 아닐수도 있다고 생각합니다.
오히려 동기부여의 측면에서 금방 싫증 날 수 있는 1번(재미) 보다 더 오래 지속되는 동기부여의 이유일수도 있고 돈 대신 보상을 줄 수 있는 긍정적인 측면도 있지 않을까? 라는 생각이 들었습니다. (하고 싶은 기술을 도전하고 있을 때는 적어도 동기가 사라지지는 않을 거니까요)
2. 적당한 속도
적당한 속도는 위의 내용과도 어느 정도 이어지는 내용인데요.
저는 속도가 너무 빠르면 압박으로 인한 스트레스 때문에 흥미를 잃게 되는 경우가 있더라고요.
사이드 프로젝트는 "사이드"라는 이름에서 나타나듯이 본업 이후에 취미로 하는 활동이라고 생각하고 있어요. 때문에, 정말 속도가 필요한 상황이 아니라면, 여유를 갖고 구성원들이 하고 싶은 것들을 충분히 할 수 있는 시간을 주는 것도 지속가능한 사이드 프로젝트에 중요한 요소가 아닐까? 라고 생각을 하고 있습니다.
신경 쓸 부분 줄이기
신경 쓸 부분을 최대한 줄여서 사이드 프로젝트에 덜 집중해도 하는것도 꾸준히 유지되는 사이드에 중요한것 같습니다.
사이드 프로젝트를 하다보면, 유저인입을 만들기위해서 꾸준히 홍보도 해줘야하고 연속적인 트랜잭션이 중간에 실패해서 이상한 상태로 남아있을경우 수기로 대응해줘야하는 경우가 있었는데요.
깃 애니몰즈에서는 이러한 부분을 자동화 시키고 싶었습니다. 1. 마케팅을 자동화하고 2. 트랜잭션 롤백을 자동화해서 수기 대응을 줄이자!
트랜잭션 롤백을 자동화해서 수기대응을 줄이자!
깃애니몰즈는 뽑기와 경매장 기능이 있는데요.
이때 각각의 기능을 위해서 여러개의 서버와 분리된 트랜잭션이 참여하게 됩니다.
여기서 트랜잭션이 물리적으로 분리되어 있기 때문에, 뽑기 중간에 서버 재배포가 발생하거나, 네트워크 이슈로 일부분의 트랜잭션만 성공했을때 롤백 처리를 해줘야할 필요가 있더라구요.
깃애니몰즈에서는 이것을 자동화 하기 위해서 Saga 를 도입했습니다.
Saga를 도입하면서 자연스럽게 스트림을 도입하게 되고 이때 다음과 같은 장점을 얻을 수 있었는데요.
모든 트랜잭션은 스트림에 publish된 후 트랜잭션을 처리하는곳에서 consume하기 때문에, 한번 이라도 publish된 saga는 어떠한 경우라도 끝까지 가게 됩니다. (consume에 실패한경우 재시도등의 처리를 해줍니다.)
즉, 뽑기나 경매장 거래 시작 이벤트가 일단 발생되면 최종 롤백이 되던 최종 성공이 되던 결과가 나오는것이죠. 이때, 모든 상태는 스트림에 기록되기 때문에, 중간에 서버가 다운되더라도 실패한 지점부터 이어서 할 수 있게 되었어요.
결과적으로는 지금까지 36,5000건의 트랜잭션을 큰 문제없이 처리할 수 있었습니다.
이때 구현으로는 이전에 만들어 놓은 Saga프레임워크(Netx)를 사용했고, 최종적으로 코드는 다음과 같이 구현이 되었습니다.
(하단 더보기 혹은 링크를 누르면 주문 Saga 코드를 볼 수 있습니다.)
package org.gitanimals.auction.app
import org.gitanimals.auction.domain.Product
import org.gitanimals.auction.domain.ProductService
import org.rooftop.netx.api.Orchestrator
import org.rooftop.netx.api.OrchestratorFactory
import org.springframework.stereotype.Service
import java.util.*
@Service
class BuyProductFacade(
private val renderApi: RenderApi,
private val identityApi: IdentityApi,
private val productService: ProductService,
orchestratorFactory: OrchestratorFactory,
) {
private lateinit var orchestrator: Orchestrator<Long, Product>
fun buyProduct(token: String, productId: Long): Product {
return orchestrator.sagaSync(
productId,
mapOf(
"token" to token,
"idempotencyKey" to UUID.randomUUID().toString()
)
).decodeResultOrThrow(Product::class)
}
init {
this.orchestrator = orchestratorFactory.create<Long>("buy product orchestrator")
.startWithContext(
contextOrchestrate = { context, productId ->
val buyer = identityApi.getUserByToken(context.decodeContext("token", String::class))
val product = productService.getProductById(productId)
require(product.getPrice() <= buyer.points.toLong()) {
"Cannot buy product cause buyer does not have enough point \"${product.getPrice()}\" >= \"${buyer.points}\""
}
productService.waitBuyProduct(productId, buyer.id.toLong())
},
contextRollback = { _, productId -> productService.rollbackProduct(productId) }
)
.joinWithContext(
contextOrchestrate = { context, product ->
val token = context.decodeContext("token", String::class)
val idempotencyKey = context.decodeContext("idempotencyKey", String::class)
identityApi.decreasePoint(token, idempotencyKey, product.getPrice().toString())
product
},
contextRollback = { context, product ->
val token = context.decodeContext("token", String::class)
val idempotencyKey = context.decodeContext("idempotencyKey", String::class)
identityApi.increasePoint(token, idempotencyKey, product.getPrice().toString())
}
)
.joinWithContext(
contextOrchestrate = { context, product ->
val token = context.decodeContext("token", String::class)
val idempotencyKey = context.decodeContext("idempotencyKey", String::class)
renderApi.addPersona(
token,
idempotencyKey,
product.persona.personaId,
product.persona.personaLevel,
product.persona.personaType,
)
product
},
contextRollback = { context, product ->
val token = context.decodeContext("token", String::class)
renderApi.deletePersonaById(token, product.persona.personaId)
}
)
.joinWithContext(
contextOrchestrate = { context, product ->
val sellerId = product.sellerId
val idempotencyKey = context.decodeContext("idempotencyKey", String::class)
identityApi.increasePointById(sellerId, idempotencyKey, product.getPrice().toString())
product
},
contextRollback = { context, product ->
val sellerId = product.sellerId
val idempotencyKey = context.decodeContext("idempotencyKey", String::class)
identityApi.decreasePointById(sellerId, idempotencyKey, product.getPrice().toString())
}
)
.commit { product -> productService.buyProduct(product.id) }
}
}
마케팅 자동화
마케팅을 자동화 하기 위해서 고민을 하다가 SNS에서 힌트를 얻었는데요.
SNS를 보면, 다른 사람이 좋아요를 누른 게시글이 친구한테 자연스럽게 추천이 되고, 추천받은 친구가 좋아요를 누르면 친구의 친구한테 추천되는 구조더라구요.
그런데, 깃허브또한 좋아요대신 스타라는 이름으로 동일한 추천? 구조를 갖고 있었습니다.
저희의 프로젝트에 스타를 누르면 스타를 누른사람의 친구한테 추천되고, 다시 친구의 친구한테 추천되는게 반복되면서 자연스럽게 퍼지게 되는거죠.
당시 저희의 프로젝트 또한 개발자 타겟이였기 때문에, 스타를 받는것에 집중을 했어요. 그래서, 홈페이지에 들어가면 스타를 누르지 않은 사람의 경우 스타를 눌러달라는 모달 알림을 주기도 했습니다.
또, SNS는 SNS서비스를 사용하는 유저에 의해서 지속적으로 홍보가 되는것 같더라구요.
깃애니몰즈도 여기서 아이디어를 얻어서, 유저가 올린 펫을 클릭하면 다시 사이트가 홍보되게끔 만들었습니다.
사실, 깃허브라는 플랫폼을 활용하기만 했을뿐 특별히 했던것은 없었는데요. 지금은 굉장히 특수한 상황이지만, 사이드 프로젝트의 타겟이 어디냐에 따라서 이미 갖춰진 플랫폼을 잘 활용했을때 꾸준한 유입을 만들어 낼 수 있다는점이 또 하나의 배운점인것 같습니다.
마치며
처음에 이런 생각을 했을때는 굉장히 큰 인사이트를 얻었다고 생각을 했던것 같은데, 적고나니 너무 당연한 이야기만 하지 않았나? 라는 반성이 드는것 같습니다. 또 깃 애니몰즈라는 하나의 프로젝트만을 통해 얻은 인사이트이기 때문에 너무 편엽한 글 일수도 있을거 같아요.
여러 지속되는 사이드 프로젝트의 사례중 깃 애니몰즈의 사례 하나일뿐이라고 생각해주시면 감사드리겠습니다. 긴 글 읽어주셔서 감사합니다.
'회고' 카테고리의 다른 글
[회고] 주니어 개발자 되기 까지 aka. 삶의지도 (0) | 2024.09.21 |
---|---|
[회고] 디프만 13기를 마치며 - 서버 개발자 (2) | 2023.07.26 |
[끄적끄적] Effective software testing 적용기 (0) | 2023.06.22 |
[짧막 회고] jpql 추상화를 무시하고 치뤄야한 대가 (0) | 2023.03.17 |