{"id":46128582,"url":"https://github.com/theiterators/lambda-days-free-workshop","last_synced_at":"2026-03-02T03:12:28.378Z","repository":{"id":49524999,"uuid":"122088566","full_name":"theiterators/lambda-days-free-workshop","owner":"theiterators","description":null,"archived":false,"fork":false,"pushed_at":"2018-03-12T23:35:26.000Z","size":105,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-03-18T00:21:45.534Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/theiterators.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-02-19T16:20:29.000Z","updated_at":"2024-03-18T00:21:45.535Z","dependencies_parsed_at":"2022-09-19T03:11:37.529Z","dependency_job_id":null,"html_url":"https://github.com/theiterators/lambda-days-free-workshop","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/theiterators/lambda-days-free-workshop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theiterators%2Flambda-days-free-workshop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theiterators%2Flambda-days-free-workshop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theiterators%2Flambda-days-free-workshop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theiterators%2Flambda-days-free-workshop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/theiterators","download_url":"https://codeload.github.com/theiterators/lambda-days-free-workshop/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theiterators%2Flambda-days-free-workshop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29991321,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-02T01:47:34.672Z","status":"online","status_checked_at":"2026-03-02T02:00:07.342Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-03-02T03:12:27.646Z","updated_at":"2026-03-02T03:12:28.371Z","avatar_url":"https://github.com/theiterators.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lambda-days-free-workshop\n\n## Purpose\n\nThe purpose of this project is to demonstrate how a REST app can be written in a completely pure, functional manner. It also serves as a playground for various ideas: tagged types, WithId, JWT, akka-http directives. Feel free to collaborate\n\n## Ideas\n\n* `tagged types` as a replacement for case-class wrappers\n    * Implementation [tag.scala](https://github.com/theiterators/lambda-days-free-workshop/blob/master/src/main/scala/pl/iterators/forum/utils/tag.scala).\n    * Usage in [domain](https://github.com/theiterators/lambda-days-free-workshop/blob/master/src/main/scala/pl/iterators/forum/domain/package.scala)\n    * See how covariance can be of importance eg. in `Id` tag\n    * Tagging happens at system edges see [CommonJsonProtocol.scala](https://github.com/theiterators/lambda-days-free-workshop/blob/master/src/main/scala/pl/iterators/forum/resources/CommonJsonProtocol.scala) and \n    [TypeMappers.scala](https://github.com/theiterators/lambda-days-free-workshop/blob/master/src/main/scala/pl/iterators/forum/repositories/interpreters/TypeMappers.scala)\n\n* `WithId` type to help with moving database ids out of domain models\n    * Implementation [WithId.scala](https://github.com/theiterators/lambda-days-free-workshop/blob/master/src/main/scala/pl/iterators/forum/utils/db/WithId.scala)\n    * In Slick tables [TableWithId.scala](https://github.com/theiterators/lambda-days-free-workshop/blob/master/src/main/scala/pl/iterators/forum/utils/db/TableWithId.scala)\n    and repositories [Repository.scala](https://github.com/theiterators/lambda-days-free-workshop/blob/master/src/main/scala/pl/iterators/forum/utils/db/Repository.scala)\n    \n* and, of course, `Free` from `cats` to glue everything together\n    * Some syntax helpers [syntax.scala](https://github.com/theiterators/lambda-days-free-workshop/blob/master/src/main/scala/pl/iterators/forum/utils/free/syntax.scala)\n    * Services as free programs [services package](https://gitlab.iterato.rs/mrzeznicki/Forum/tree/de6f1131003e0f1163daf104a0d89cd4ec803834/src/main/scala/pl/iterators/forum/services)\n    * Repositories as interpreters [repositories package](https://gitlab.iterato.rs/mrzeznicki/Forum/tree/de6f1131003e0f1163daf104a0d89cd4ec803834/src/main/scala/pl/iterators/forum/repositories)\n    \n* Smaller things like:\n    * JWT support [JwtSupport.scala](https://github.com/theiterators/lambda-days-free-workshop/blob/master/src/main/scala/pl/iterators/forum/resources/JwtSupport.scala)\n    * i18n using Rapture [i18n package](https://gitlab.iterato.rs/mrzeznicki/Forum/tree/de6f1131003e0f1163daf104a0d89cd4ec803834/src/main/scala/pl/iterators/forum/domain/i18n)\n    * akka-http helpers [Resource.scala](https://github.com/theiterators/lambda-days-free-workshop/blob/master/src/main/scala/pl/iterators/forum/resources/Resource.scala)\n    * Algebraic domain modelling eg. [Account.scala](https://github.com/theiterators/lambda-days-free-workshop/blob/master/src/main/scala/pl/iterators/forum/domain/Account.scala)\n    or [Post.scala](https://github.com/theiterators/lambda-days-free-workshop/blob/master/src/main/scala/pl/iterators/forum/domain/Post.scala)\n    * Using rollbacks and fixtures to easily test db [Base.scala](https://github.com/theiterators/lambda-days-free-workshop/blob/master/src/it/scala/pl/iterators/forum/fixtures/Base.scala)\n    and [Accounts.scala](https://github.com/theiterators/lambda-days-free-workshop/blob/master/src/it/scala/pl/iterators/forum/fixtures/Accounts.scala)\n    \n## Architecture\n\nArchitecture is based on ideas from `Functional and Reactive Domain Modelling`. It retains 3-layered approach: there are services, resources and repositories. The meaning of key components differs from what you'd expect from non-Free program, though.\n\n### Service\n\nService constructs a `Free` program consisting of ADTs found in repositories. Thus, you can say, that repository formulates a language, which any services can use to build a complete functionality.\nService is normally a trait which imports repositories for ADTs and domain objects. Services are completely pure and testable. For example, to store a post:\n\n```scala\n  def newPost(threadId: ThreadId, authorId: AuthorId, postContent: PostContent): PostOperation[Either[PostError, FullPostWithId]] =\n    lookupThread(threadId)\n      .toEither(ifNone = NoSuchThread)\n      .subflatMap(thread =\u003e Either.cond(test = !thread.isClosed, thread, ThreadClosed))\n      .flatMapF(thread =\u003e PostRepository.storePost(thread.id, authorId, postContent))\n      .value\n\n  def lookupThread(id: ThreadId)                    = PostRepository.lookupThread(id)\n\n```\n\n\nObviously - secret of testability lies in services' independence of actual interpretation and ability to swap interpreters\n\nMore sophisticated examples include configurable operations eg.\n\n```scala\n  def passwordPolicy: PasswordPlain =\u003e Either[PasswordTooWeak.type, String]\n  def confirmationTokenTtl: Duration\n\n  def update(id: AccountId, accountChangeRequest: AccountChangeRequest): AccountOperation[StoreResult] =\n    (for {\n      _       \u003c- EitherT.fromEither[AccountOperation](accountChangeRequest.validatePassword(passwordPolicy))\n      updated \u003c- EitherT(AccountRepository.update(id, accountChangeRequest.updateFunction))\n    } yield updated).value\n\n  def confirm(accountConfirmRequest: AccountConfirmRequest): ConfirmationTokenWithAccountOperation[Either[ConfirmationError, Ok.type]] =\n    ConfirmationTokenOrAccount\n      .queryToken(accountConfirmRequest.email, accountConfirmRequest.confirmationToken)\n      .toEither(ifNone = InvalidToken)\n      .subflatMap(confirmationToken =\u003e\n        Either.cond(test = !confirmationToken.isExpired(confirmationTokenTtl), accountConfirmRequest.email, TokenExpired))\n      .flatMapF(email =\u003e ConfirmationTokenOrAccount.setConfirmed(email, accountConfirmRequest.nick))\n      .value\n\n```\n\nTests can fake anything\n\n```scala\n  val accountService = new AccountService {\n    private val passwordMinLength = 4\n\n    override val passwordPolicy = (password: PasswordPlain) =\u003e\n      for {\n        _ \u003c- mustBeOfLength(passwordMinLength)(password)\n        _ \u003c- mustContainLetter(password)\n        _ \u003c- mustContainUpper(password)\n      } yield password\n\n    override val confirmationTokenTtl = Duration.ofMinutes(1)\n    override val messages = new Messages {\n      override val from = EmailAddress(\"noreply@example.com\".@@[Email], name = Some(\"no-reply\"))\n    }\n\n  }\n\n```\n\nAlso, you can test things that happen _out-of-band_ like sending emails plus its complex interactions with generating tokens:\n\n```scala\n type EmailLog[V] = Writer[List[EmailMessage], V]\n\n  protected def writeEmailLogValue: Id ~\u003e EmailLog = λ[Id ~\u003e EmailLog](Writer.value(_))\n  def mailingLogger: MailingRepository ~\u003e EmailLog =\n    λ[MailingRepository ~\u003e EmailLog] {\n      case SendEmail(message) =\u003e Writer(List(message), Ok)\n    }\n  val confirmationTokenOrEmailLoggingOrAccountInterpreter\n    : ConfirmationTokenOrEmailOrAccount ~\u003e EmailLog = (accountInterpreter andThen writeEmailLogValue) or (mailingLogger or (tokenInterpreter andThen writeEmailLogValue))\n    \n  it(\"should send confirmation email\") {\n     new AccountFixture with ConfirmationTokenFixture with MailingFixture {\n        import cats.instances.list._\n\n        val log = createF(AccountCreateRequest(\"user@example.com\".@@[Email], \"Dr56::sf\".@@[Password]))(confirmationEmailEnv).value\n          .foldMap(confirmationTokenOrEmailLoggingOrAccountInterpreter)\n\n        val emails = log.written\n        emails should have length 1\n        \n        val token = tokenInterpreter.find(\"user@example.com\".@@[Email]).head\n\n        emails.head should matchPattern {\n          case accountService.messages.ConfirmationMessage(email, _, link)\n              if email == \"user@example.com\" \u0026\u0026 link == confirmationEmailEnv.confirmationLink(\"user@example.com\".@@[Email], token) =\u003e\n        }\n     }\n  }\n\n\n```\n\n### Repository\n\nRepository is **not** a db-repository where you'll typically find queries or tables but rather collection of ADTs for describing interaction with storage. For instance\n\n```scala\nsealed trait MailingRepository[A]\n\nobject MailingRepository {\n  case class SendEmail(message: EmailMessage) extends MailingRepository[Ok.type]\n\n  def sendEmail(message: EmailMessage) = Free.liftF(SendEmail(message))\n}\n\n``` \n\nThese are used by services to create complex scenarios. Eventually, you'll write some specific **interpreters** for these eg. backed by a db. But these are implementation details. Also, please note that Slick stuff is called repository too - but it is in utils [Repository.scala](https://github.com/theiterators/lambda-days-free-workshop/blob/master/src/main/scala/pl/iterators/forum/utils/db/Repository.scala).\nAs such, it is only **used** by a specific interpreter. So - in a nutshell what you treated as Slick repo is now broken down into two things:\ncollection of generic queries (found in utils) plus model and specific queries (as interpreter). \n\nRepositories are tested usually in `it:test` (because they involve some external system as SMTP server or a DB) \n\n### Resources\n\nResources are facing outside world via HTTP interface - sometimes you call them routers. They demand a service (for making a program) and an interpreter (for, well, interpreting it - usually to a `Future`)\nand push the result to the caller via `akka-http`. Typical interaction:\n\n```scala\ntrait PostsResource extends Resource with PostsResource.PostsProtocol {\n  def postService: PostService\n  def postRepositoryInterpreter: PostRepositoryInterpreter\n\n  import PostsResource._\n  import cats.instances.future._\n\n  private def runCreateNewThread(authorId: AuthorId, newThreadRequest: NewThreadRequest) =\n    postService.newThread(authorId, newThreadRequest) foldMap postRepositoryInterpreter\n  protected lazy val createNewThread: Route = (post \u0026 extractClaims) { claims =\u003e\n    val authorId = claims.id.asAuthorId\n\n    entity(as[NewThreadRequest]) { newThreadRequest =\u003e\n      withBaseUri(implicit uri =\u003e onSuccess(runCreateNewThread(authorId, newThreadRequest))(completeWithLocation))\n    }\n  }\n\n  private def runLookupThread(threadId: ThreadId) =\n    postService.lookupThread(threadId) foldMap postRepositoryInterpreter\n  protected val lookupThread: ThreadId =\u003e Route = id =\u003e\n    get {\n      jwtAuthorize() {\n        onSuccess(runLookupThread(id)) {\n          case None =\u003e complete(NotFound)\n          case Some(thread) =\u003e\n            withBaseUri(implicit uri =\u003e completeAsResource(thread))\n        }\n      }\n  }\n\n  val threadsRoutes: Route = pathEndOrSingleSlash { createNewThread ~ fetchThreads } ~\n    pathPrefix(LongNumber.map(ThreadId(_))) { threadId =\u003e\n      pathEnd { lookupThread(threadId) } ~\n        pathPrefix(\"posts\") {\n          pathEnd { createNewPost(threadId) ~ fetchPosts(threadId) } ~\n            path(LongNumber.map(PostId(_))) { postId =\u003e\n              lookupPost(threadId)(postId) ~ updatePost(threadId)(postId)\n            }\n        }\n    }\n}\n\n```\n\nThey are also totally unit-testable. \nSometimes they need to provide some more complex environment (for example, to send an email), or combine multiple interpreters together\n\n```scala\ntrait AccountsResource extends Resource with AccountsResource.AccountsProtocol with LanguageSupport {\n  def accountService: AccountService\n  def accountRepositoryInterpreter: AccountRepositoryInterpreter\n  def confirmationTokenInterpreter: ConfirmationTokenRepositoryInterpreter\n  def mailingInterpreter: MailingRepositoryInterpreter\n\n  def confirmationLinkTemplate: String\n\n  import AccountsResource.{AccountsResourceObject, MailEnv}\n  import cats.instances.future._\n\n  private def withMailEnv = extractHost.flatMap { host =\u003e\n    determineLocale().flatMap { locale =\u003e\n      provide(new MailEnv(locale, host, confirmationLinkTemplate))\n    }\n  }\n\n  private def runCreateAccount(accountCreateRequest: AccountCreateRequest)(mailEnv: ConfirmationEmailEnv) =\n    accountService\n      .createRegular(accountCreateRequest)(mailEnv)\n      .value foldMap (accountRepositoryInterpreter or (mailingInterpreter or confirmationTokenInterpreter))\n  protected val createAccount: Route = (post \u0026 entity(as[AccountCreateRequest])) { accountCreateRequest =\u003e\n    withMailEnv { mailEnv =\u003e\n      onSuccess(runCreateAccount(accountCreateRequest)(mailEnv)) {\n        case Left(error)          =\u003e complete(Conflict -\u003e error)\n        case Right(accountWithId) =\u003e completeWithLocation(accountWithId)\n      }\n    }\n  }\n}\n```\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheiterators%2Flambda-days-free-workshop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftheiterators%2Flambda-days-free-workshop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheiterators%2Flambda-days-free-workshop/lists"}