「Scaling Teams 開発チーム組織と人の成長戦略」を読んで

本を手にとった理由

自分は10人程度の組織を4つ束ねた課のマネージャーでプロダクトが期待されるスコープは急拡大している。自然と組織のサイズが大きくなっているのでプロダクトスコープの拡大に組織の体制が追従していく必要がある。すでに感覚的には限界やリスクを感じている部分があるので拡大していく組織の生産性を維持もしかくは向上させるアイデアが欲しかった。まえがきを読んでみるとハイパーグロース中の組織のマネージメントを念頭においているような節があったが既にある程度成熟している大企業でも適応可能なアイデアや原則を吸収したいと思う。 特に組織デザインのスタイルとしてプロダクトラインでバーティカルに組織化するか職能ラインでホリゾンタルに組織化するのかハイブリットか。。。もし答えはなくとも原理原則に触れられれば嬉しい。

いつもは前から順に読んでいくスタイルだが、今回は興味のある章から順に読んでいくスタイルを試す。 採用と文化についてはななめ読みした。

まとめ

バリューストリームとデリバリー組織を基本とした組織構成について何度もしつこく解説してくれたおかげで今向き合っている組織を最適化していく指針が得られた。 組織のありかたに唯一の正解はないどの体制を取ってもプロコンが出るので候補を比較し採用した方式の弱点を理解し、補うアプローチを取りたいと思う。

ポイント

6章:組織設計の原則

この章は「規模の不経済の回避」をテーマにしている。 エンジニアリング組織論の「技術組織の力学とアーキテクチャ」で述べられていた部分と重なる部分が多かった

特に増員とアウトプットの関係性、自律性と身勝手の違やバランスのとり方については特に詳しくのべられていたのでこの章を読んだあと読み直した。 両方の本から得たことを合わせて書いていこうと思う

増員とアウトプットの量

アウトプットとは組織の情報処理能力であり、情報処理能力は組織の人員が増えるとある点からは急激に低下すると説明している。 情報処理能力が低下するのは情報の非対称性が原因

バリューストリームをの断絶リスク

バリューストリームをの断絶を招くような組織デザインではなく複数の職種からなる「着想〜公開」を95%以上独力で行うことができる組織デザインを勧めている。 そのよなチームのことと「デリバーリーチーム」と重んじるべき価値として内発的動機づけの基軸である自律性・熟達・目的を背景に5つの原則を提示している。 バリューストリームを断絶する組織デザインが原則を遵守することの困難さは * 「着想〜公開」をチーム内で完遂できない * バリューストリームの下流では顧客との接点が薄れ目的意識が希薄化 * 組織間の合意形成にかかる時間が継続的デリバーリーを間延びさせフィードバックの機会を奪う 「着想〜公開」までを内部で完結するチームで困難を回避し原則をフォローするように務める

自律性の限度(権限と責任)

自律性の範囲は?何でも組織の意思で決定できるのか? 適切な自律の範囲を維持させるのは非常に難しい。権限は株主から一般社員へ経営陣、管理職を経て段階的に委譲される。 権限とは会社の資産・リソースを自由に使うことができる権利である。責任は権限を行使したことでもたらした結果を説明する責務だ。

委譲と成功度の測定

上司は信頼して権限を委ねた上で結果の説明を求める必要がある。 そのとき結果は明らかに測定可能であり会社の目的に沿ったものでなくてはならない。 おそらくいつでも数値で表せるほど現実は単純ではないだろうから数値目標の改善を長期目標に据えてた重要行動計画(Key Action)を定義してアクションがResultに及ぼす影響を計測できる仕組みを追い求めるべきだと思う。

7,8章:組織のスケーリング:デリバリーチームと報告体制

7,8章では具体的な例をあげながら原則の適応方法について議論している。 組織編成、レポートラインにおいてもバリューストリームとデリバリー組織の原則を維持する考え方基本にしている。 どの会社、組織にでも適応できる組織構成など存在しない、現状のチームの能力やプロダクトが置かれている状況を理解してその時々に最適なあり方に修正するだけ。原則は最適化の指針になる。

「マーケティングオートメーションでおもてなし」を読んで

本を手にとった理由

自分はマーケティングテックの中でもMAとメール配信にテック側の人間として深く関わっているのでマーケティングオートメーションをマーケターの視点から解説した本を読んでドメインの理解を深めたいと思った。言うまでもなく何のためのソリューションを作っているのか理解することは非常に大切で現場のマーケターもマーケティングオートメーションの本質を分かっていない場合があるからだ。

まとめ

  • マーケティングオートメーションに関する語彙について体系的に背景や目的を理解できた。それぞれの関連性や類似性にも触れることができて今後仕事で自信を持って言葉を選ぶことができるようになったと思う。
  • 打ちてを考える・実装するときに背景にある目的をマーケティングオートメーションの特定領域にマップできると考えを整理しやすいしブレにくい。CRMの9象限を意識して考える。
  • マーケターがどれだけプランニングに時間を使えているかがシステム側の成功の物差し

ポイント

めざすべき姿

自社のマーケティングを「昔なじみの御用聞きにする」

  • 買ったもの、興味のあるものを覚えていてくれる
  • 近江商人の三方良しの精神:お客様が心底満足して取引先、地域社会ひいては一般社会の役に立つ

ダイレクトマーケティング

  • ダイレクトマーケティングの第一声、メールの場合Subjectは「自分ごと化」スイッチオンに集中
  • ブランディング広告とは根本的に異なる
  • ダイレクトマーケティングの成果は商品、取引条件、ロジ、コミュニケーションだが、マーケターが影響力を発揮できるのはコミュニケーションのみなのでコミュニケーションで成果が決まる。 コピーライティングを工夫すると成果を大きく伸ばせるかも=表現力 メディアの特性を理解して使い分け連携させる=メディアプラン

レコメンデーション

ラダリングによる価値観の発見はJob理論に通じつ考え方だと思った。 同じ商品でも異なる価値観(Job)による動機で購入されることがあるだろう。つまり価値観やコンテキストに沿ったコミュニケーションが「自分ごと化」へ近づく道だ。

難しさ:ダイレクトマーケティングの落とし穴

過度な「売らんかな姿勢」は危険 ハイブランドのコミュニケーションはブラインドイメージの維持のために安っぽいキャンペーン「今だけとかおまけとか」をやらない。 ハイブランドのコミュニケーションは「洗練されたエンターテイメント」→所有の喜び満足感に繋がる 数字(KPI)にコミットするダイレクトマーケティングはブランドイメージの毀損を招きやすい ブランドイメージの既存は購入時の安心感・満足感を低下させる 負の影響から目を背ける姿勢は中長期的な顧客離反や顧客獲得コストLTVの減少という形で現れる

メール

  • メールはバックエンドメディアでありマスのコミュニケーション用途ではない
  • メールを送信するときはお客様の個人情報を獲得してある程度趣向を理解していることが前提
  • メールに対する反応の反応をシナリオとして設計してしてないのはコミュニケーションの責任放棄

CRM

  • 上顧客が何故上顧客なのか本質的な答えが必要。デモグラフィックデータの共通点などではなくラダリングの一番上、あるいは「雇用する理由」を知りたい
  • 時間軸は良い着眼点だが根本的な価値観への理解が時間軸と噛み合うと非常に協力に作用すると思う。
  • ペンディングとロイヤリティの2軸を使って9象限で分けたマップと顧客数のプロット、象限間移動の量と理由の分析が大事

リードジェネレーション・リードナーチャリング

  • リードジェネレーションは潜在顧客を顧客に育成するだけではなくまだニーズが顕在化していない層に対してニーズに気づいてもらい、見込み顧客化するとろまでを含む
  • マスメディアに比肩する武器はソーシャルメディア

Spring boot admin でAplicationのメトリックスを可視化

Spring boot adminはSpring bootで作られたアプリケーションの
管理者向けGUIツールです。
こんな風にSpring actuatorで提供されている情報を美しく表示したり
メモリの使用量等も見やすいですね。
https://raw.githubusercontent.com/codecentric/spring-boot-admin/master/screenshot-details.png

こんな風にアプリのログレベルをロガーごとにオンラインで変更できます。
https://raw.githubusercontent.com/codecentric/spring-boot-admin/master/screenshot-logging.png


導入方法は非常にシンプルです。
基本的には公式に書いてある通りにやればOKです。
https://github.com/codecentric/spring-boot-admin

構築にはeureka(service discovery)の仕組みを用いて
間接的にサーバーがクライアントを認識する方法と
クライアントから直接サーバーに存在を通知する方法の2通りあります。
今回は後者のクライアントから直接サーバーにクライアントの存在を通知する方法を取ります。
eurekaはまだ勉強中なので・・・

冗長構成の取り方等まだまだ調べることがありますが、運用支援ツールなので
今のところそこまで可用性を求めていません。
落ちていてもサービス維持に直結しませんから

サーバー

https://start.spring.io/ に行って適当にひな形プロジェクトを作りました。
pom.xmlにspring boot adminのserverとuiを追加します。

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-server</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-server-ui</artifactId>
    <version>1.2.3</version>
</dependency>

@EnableAdminServerを追加してサーバー機能を有効化します。

@Configuration
@EnableAutoConfiguration
@EnableAdminServer //ここ重要
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

application.propertiesを作成して任意のポートを指定しておきます。
server.port=8888

クライアント

pom.xmlにclientの依存を追加

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>1.2.3</version>
</dependency>

application.propertiesに先ほど立てたサーバーのサーバー名とPort番号を設定します。
spring.boot.admin.url=http://localhost:8888

私はspring actuatorのパス(management.context-path)を変更しているのですが、
Spring boot adminは特にそのパスを明示的に定義しなくても動作しました。

Spring RestTemplate でBasic認証

SpringのRestTemplateでBasic認証を求めてくるサーバーに接続する必要がある場合
少し調べたら全てのendpointで

String plainCreds = "username@password";
byte[] plainCredsBytes = plainCreds.getBytes();
byte[] base64CredsBytes = Base64.encodeBase64(plainCredsBytes);
String base64Creds = new String(base64CredsBytes);

HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Creds);

HttpEntity<String> request = new HttpEntity<String>(headers);
ResponseEntity<Account> response = restTemplate.exchange(url, HttpMethod.GET, request, Account.class);
Account account = response.getBody();

てなことをする必要あり、もっと認証をリクエスト時に意識せずに
書ける方法がないか調べてみました。
postForObject使いたかったしね

以下調べた方法

ApacheのHTTP clientを使ってBasic認証を通過するようにしてやる。
AuthScope.ANYにしているところで認証を使うアクションを絞ることもできそうですが、
今回は全てのendpointで必用なのでこれでOK

BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
        new UsernamePasswordCredentials("username", "password"));

HttpClientBuilder httpClientBuilder =
        HttpClientBuilder.create().setDefaultCredentialsProvider(credentialsProvider);
ClientHttpRequestFactory factory =
        new HttpComponentsClientHttpRequestFactory(httpClientBuilder.build());
new RestTemplate(factory)

pom.xmlapache http clientt追加
versionはSpring IOにおまかせ

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
</dependency>

Spring Data RedisでSentinelに接続する

可用性を確保するためにRedis+Sentinelを利用している場合は、
クライアントアプリケーションから直接Redisのマスタを参照せずにクライアントライブラリに対して
Sentinelのノード情報を教えてあげることでマスターがFail overしたときに自動的にクライントが
Fail overに追従できます。
Spring Data Redisを使ってSentinelの設定を行う方法を説明ましす。
参考(5.4. Redis Sentinel Support):
http://docs.spring.io/spring-data/redis/docs/current/reference/html/#redis:sentinel

@Bean
public RedisConnectionFactory jedisConnectionFactory() {
  RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration() .master("mymaster")
  .sentinel("127.0.0.1", 26379) .sentinel("127.0.0.1", 26380);
  return new JedisConnectionFactory(sentinelConfig);
}

こうすることでクラスタの名前(mymaster)とSentinelのノード(普通は3台以上)を定義できますが、
ハードコーディングはしませんよね?

application.propertiesに定義できます。

spring.redis.sentinel.master: mymaster

spring.redis.sentinel.nodes: 127.0.0.1:26379,127.0.0.1:26380

設定ファイルにsentinelの情報を逃すとSpringが設定情報を注入してくれます。

@Bean
public RedisConnectionFactory jedisConnectionFactory() {
  return new JedisConnectionFactory();
}

SpringDataRedisを使ってJson形式でオブジェクトをRedisに保存する方法

SpringDataRedisを使ってObjectを保存するとデフォルト設定ではKey、Value共に可読性の悪いserializeされたデータになります 

テストや運用の効率を考慮して可読性の高いJson形式でデータを保存したくなったので方法を記録しておきます。

pom.xml

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-redis</artifactId>
</dependency>
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
</dependency>
<!-- Json に変換するために必要 -->
<dependency>
  <groupId>org.codehaus.jackson</groupId>
  <artifactId>jackson-mapper-asl</artifactId>
  <version>1.9.13</version>
</dependency>

Configクラス

@Configuration
@ComponentScan(basePackages="redisSample")
@EnableWebMvc
public class WebApplicationConfig extends WebMvcConfigurerAdapter {
    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory cf = new JedisConnectionFactory();
        cf.setUsePool(true);
        cf.setHostName("localhost");
        cf.setPort(6379);
        return cf;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> rt = new RedisTemplate<String, Object>();
        rt.setConnectionFactory(jedisConnectionFactory());
        // key のSerialize方法を指定
        rt.setKeySerializer(new StringRedisSerializer());
        // Value のSerialize方法を指定
        rt.setValueSerializer(new JacksonJsonRedisSerializer<>(Object.class));

        return rt;
    }
}

コントローラー

オブジェクトを出したり入れたりする

@RestController
@EnableWebMvc
public class SampleController {

    @Autowired
    RedisTemplate<String, Object> redisTemplate;

    @RequestMapping(value = "/getData", method = RequestMethod.GET)
    public void getData(@RequestParam("key") String key, HttpServletResponse res) throws IOException {
        res.getWriter().write(redisTemplate.opsForValue().get(key).toString());
    }

    @RequestMapping(value = "/setData", method = RequestMethod.GET)
    public void setData(@RequestParam("key") String key,@RequestParam("value") String value, HttpServletResponse res) throws IOException {
        SampleDto dto = new SampleDto();
        dto.value = value;
        dto.creationDateTime = System.currentTimeMillis();
        redisTemplate.opsForValue().set(key, dto);
        res.getWriter().write("OK");
    }
}

保存するオブジェクト

public class SampleDto {
    public String value;
    public long creationDateTime;
}

確認

起動

java -jar target/redisSample-0.0.1-SNAPSHOT.jar

set

wget "http://localhost:8080/setData?value=hoge&key=key1"

get

wget "http://localhost:8080/getData?key=key1"
cat getData\?key\=key1
# {value=hoge, creationDateTime=1440077026083}

redis

get key1
# "{\"value\":\"hoge\",\"creationDateTime\":1440077026083}"


github.com

Spring開発環境構築 Spring Tool Suite(STG)に追加のプラグイン入れるまで

MacでSpringの開発環境を作ったときの記録を残しておきます。

※本当はIntellijを使いたかったけどライセンスを購入してもらえるまでSTSで我慢します。

STSをインストールした時点で入っていない追加でインストールが必要と思ったプラグインの入れ方までを説明します。

Mac初心者なのでMarketplacesを使わずにインストールする方法でつまづきました。

STSをダウンロードして展開

https://spring.io/tools

Pleiades (プレアデス)

複数プラグインを一度に導入できます。私が絶対必要と思ったのは下記です。

インストール方法

STSを起動してEclipseのバーションを確認します。

Help → Instlation Details

4.5でした。

f:id:Greenkun:20150730230039p:plain

PlediesからEclipseのバージョンと同じバージョンのzipをダウンロードします。

http://mergedoc.osdn.jp/

本体はSTSを使いますのでStanderd editionのUltimateを選択します。

ダウンロードできたら展開します。

Plediesに含まれているプラグインSTSにインストールします。

STSのインストールディレクトリを開ます。(/Applications/sts-bundle)←私はここに置きました。

Finderで開くと葉っぱのアイコンになっていて分かり辛いのですがターミナルで確認するとSTS.appというディレクトリがあるのでコピー先のディレクトリまでターミナルで移動してターミナルからFinderを開いて作業しました。

open /Applications/sts-bundle/STS.app/Contents/Eclipse/dropins

f:id:Greenkun:20150730230239p:plain

STSのdropoinsディレクトリを開いたらPlediesのeclipse/dropinsに含まれているディレクトリをコピーします。

STSを起動してプラグインが入っているか確認して終了

Plediesは日本語化が主な機能の一つのようですが、外国人もいる職場なので頑張って英語のまま使います。

Marketplaceから入れたプラグイン

最後にPlediesには含まれていなかったカバレッジ測定ツールを入れました。

EclEmma(コードカバレッジ)