Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ShareDocs の紹介

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for benjamin benjamin
September 22, 2017
330

ShareDocs の紹介

Avatar for benjamin

benjamin

September 22, 2017
Tweet

Transcript

  1. 概要 • ナレッジ共有ツール • 2017/01 に OSS として公開 ◦ もともとは社内で使ってたナレッジ共有ツール

    ◦ 「Scala で実装した社内ナレッジ共有ツールを OSS にして公開しました」 ▪ http://tech.atware.co.jp/sharedocs/ • github リポジトリ ◦ https://github.com/atware/sharedocs
  2. 話すこと • ShareDocs とは ◦ 作った経緯など ◦ なにができるのか • とりあえず実装しておきたい追加機能の実装例

    ◦ 記事投稿時の通知 ◦ 記事へのコメント時の通知 ◦ コントリビューション数の監視と通知 ShareDocsの紹介します ※ SkinnyFrameworkの話はあまり出てこないです m(_ _)m
  3. ShareDocs • とりあえずアプリケーションの動きを見たい • 環境構築 ◦ README 見る ◦ 手順通りにやると

    ./sharedocs db:migrate でエラー吐く ▪ SharedocsEnv を作ってからやり直し • 必要に応じて環境変数を変える ◦ LOGIN_PROVIDOR=google # google+、LDAP、メール ◦ DATABASE****
  4. ShareDocs • 自分が開発する時に参考にしたもの ◦ Skinny Framework Getting Started 日本語版 ▪

    http://seratch.hatenablog.jp/entry/2016/05/23/220911 ▪ 流れで ShareDocs も見れるようになる ◦ Skinny Framwework - Dependency Injection - ▪ http://skinny-framework.org/documentation/dependency-injection.ht ml • とりあえず controller.Controllers のコードを掘っていけばいい
  5. とりあえず追加で入れときたい機能 • 記事が投稿された時に Slack に通知する • 記事にコメントがついたときに Slack に通知する •

    コントリビューション数が一定閾値に達した時に Slack に通知する あるともうちょい盛り上がれる
  6. 記事投稿時 & 記事コメント時の通知 • ArticlesController#create • 記事作成時に ExternalServiceIntegration#onPostCreated class ArticlesController

    extends ApplicationController { def create = { ... // Notify to external service val externalServiceIntegration = inject[ExternalServiceIntegration] externalServiceIntegration.onPostCreated(loginUser.get, article) ... } } }
  7. 記事投稿時 & 記事コメント時の通知 • ****Integration という名前のクラスを探して DI • **** の部分は

    SkinnyConfig で読む class IntegrationsModule extends scaldi.Module { private val DEFAULT_SERVICE = "Null" val service = SkinnyConfig.stringConfigValue("externalIntegration.service").map { configValue => configValue.capitalize } getOrElse DEFAULT_SERVICE import scala.reflect.runtime.universe val runtimeMirror = universe.runtimeMirror(getClass.getClassLoader) val module = runtimeMirror.staticModule(s"integration.${service}Integration") val obj = runtimeMirror.reflectModule(module) val integration = obj.instance bind[ExternalServiceIntegration] to integration.asInstanceOf[ExternalServiceIntegration] }
  8. 記事投稿時 & 記事コメント時の通知 • ExternalServiceIntegration という抽象クラスを継承したクラスを作成する • onPostCreated ◦ 記事が投稿された時にしたい処理を実装する

    • onCommentCreated ◦ 記事にコメントがついた時の処理を実装する • デフォルトでは NullIntegration という実装が使われる(何も処理しない) ◦ ****Integration の実装は環境変数 EXTERNAL_INTEGRATION_SERVICE=**** で指定する object NullIntegration extends ExternalServiceIntegration { override def onCommentCreated(commenter: User, comment: Comment)(implicit ctx: SkinnyContext): Unit = {} override def onPostCreated(author: User, article: Article)(implicit ctx: SkinnyContext): Unit = {} }
  9. 記事投稿時 & 記事コメント時の通知 • Slack の Web API 呼ぶ •

    https://api.slack.com/methods/chat.postMessage class Slack(channelName: String, token: String, botImageUrl: String) extends JSONStringOps with Logging { def post(message: String): Unit = { val request = Request(Slack.urlChatPostMessage) .queryParams("token" -> token) .queryParams("channel" -> channelName) .queryParams("text" -> message) .queryParams("icon_url" -> botImageUrl) .enableThrowingIOException(true) val response = HTTP.post(request) if (response.status == 404) logger.info(s"Failed to post a message to the channel $channelName") else logger.info(response.textBody) } }
  10. 記事投稿時 & 記事へのコメント時の通知 • 記事投稿時 object SlackIntegration extends ExternalServiceIntegration with

    LoggerProvider { override def onPostCreated(author: User, article: Article)(implicit ctx: SkinnyContext): Unit = { import scala.concurrent.ExecutionContext.Implicits.global val articleUrl = s"""${Util.baseURL(ctx.request)}/articles/{article.articleId}?${OGP_PARAM_NAME}""" val message = s""" |${author.name}さんが新しい記事を投稿しました! |「${article.title}」 |$articleUrl """.stripMargin Slack().postAsync(message) }
  11. 記事投稿時 & 記事へのコメント時の通知 • 記事へのコメント時 override def onCommentCreated(commenter: User, comment:

    Comment)(implicit ctx: SkinnyContext): Unit = { val article = Article.findById(comment.articleId).get val articleUrl = s"""${Util.baseURL(ctx.request)}/articles/${comment.articleId}#comment-${comment.commentId}""" val message: String = s""" |${commenter.name}さんが${author.name}さんの記事にコメントしました! |「${article.title}」へのコメント |$articleUrl """.stripMargin Slack().postAsync(message) } }
  12. • SkinnyWorker を継承したクラスを作成する • init() に初期化時の処理を実装する コントリビューション数を監視して通知 class ContributionCelebrationWorker extends

    SkinnyWorker { val THRESHOLD = 5 val BASE_URL = "http://localhost:8080" val cache = new Cache[UserId, Long]() def init(): Unit = { User.findAll().map(u => u.userId).foreach(userId => { val contribution = User.calcContribution(userId) cache.put(userId, contribution) }) } }
  13. • 全ユーザのコントリビューション数を取得する • 閾値チェック & 通知 & キャッシュ コントリビューション数を監視して通知 override

    def execute(): Unit = { def shouldCelebrateContribution(before: Long, now: Long): Boolean = (before / THRESHOLD < now / THRESHOLD) && (now % THRESHOLD > 0) User.findAll().foreach(user => { val contribution = User.calcContribution(user.userId) cache.get(user.userId).foreach(before => { if (shouldCelebrateContribution(before, contribution)) { val message = s""" |${contribution / THRESHOLD * THRESHOLD} Contribution! | ${user.name} |$BASE_URL/users/${user.userId}""".stripMargin Slack().post(message) }}) cache.put(user.userId, contribution) })
  14. • Bootstrap クラスで worker を起動する • skinnyWorkerService.everyFixedSeconds(worker, 30) などで実行するタイミン グを指定する

    コントリビューション数を監視して通知 class Bootstrap extends SkinnyLifeCycle { scalikejdbc.GlobalSettings.loggingSQLAndTime = scalikejdbc.LoggingSQLAndTimeSettings( singleLineMode = true) val worker = new ContributionCelebrationWorker override def initSkinnyApp(ctx: ServletContext): Unit = { skinnyWorkerService.everyFixedSeconds(worker, 30) ctx.mount(classOf[SkinnySessionInitializer], "/*") Controllers.mount(ctx) } }
  15. その他 • メール認証できてなかった問題 ◦ http://miyako.hatenablog.jp/entry/2017/02/13/222604 ▪ > 「実はメール認証した時にメールが飛ばない(--;)」 ◦ docker

    イメージにも SMTP サーバ含まれてない ▪ 入れる予定 • タイポとかの指摘はコメントするまでもないので private に伝えられる機能が欲しい
  16. まとめ • ShareDocs は社内ナレッジ共有ツールとして使える OSS • SkinnyFramework 製で、ベター Java として

    Scala 触りたい時にも使える • ちょっとした追加機能の実装例 ◦ 記事投稿時 & 記事へのコメント時の通知 ▪ ExternalServiceIntegration 抽象クラスを継承したクラスを作成する ▪ sharedocs.conf の externalIntegration.service でクラス名のプレフィックスを指定する ▪ 指定したクラスを DI して使ってくれる ◦ コントリビューション数の監視 ▪ 定期実行系の処理は SkinnyWorker を使うのが手っ取り早い ▪ Bootstrap クラスで worker を起動する ▪ SkinnyLifeCycle.skinnyWorkerService#everyFixedSeconds などで実行するタイミングを指 定する