MONEX ENGINEER BLOG │マネックス エンジニアブログ 2024-03-27T18:20:45+09:00 monex_engineer Hatena::Blog hatenablog://blog/10257846132626900973 Using Amazon Bedrock, Anthropic Claude for Langchain Tools and Agents hatenablog://entry/6801883189092500951 2024-03-27T18:20:45+09:00 2024-03-27T18:22:03+09:00 Overview This article is about how we can use LLMs to create an application that can interact with users to fetch proprietary data via APIs and let the LLM consume or pass along that data to users based on user's question. We will use Amazon Bedrock, Anthropic Claude, Langchain and Monex Insight Mar… <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/nzkkun/20240321/20240321164303.png" alt="Amazon Bedrock" /></p> <h2 id="Overview">Overview</h2> <p>This article is about how we can use LLMs to create an application that can interact with users to fetch proprietary data via APIs and let the LLM consume or pass along that data to users based on user's question. We will use Amazon Bedrock, Anthropic Claude, Langchain and Monex Insight Market Data APIs to create a simple proof of concept on how that can be achieved.</p> <h3 id="Amazon-Bedrock">Amazon Bedrock</h3> <p>Amazon Bedrock is a fully managed service that offers choice of foundation models (FMs) from leading AI companies like Ai21 Labs, Anthropic, Cohere, Meta, etc and also Amazon via a single API. More information about Amazon Bedrock can be found in <a href="https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html">References</a>.</p> <h3 id="Anthropic-Claude">Anthropic Claude</h3> <p>For this blog, we will focus on Anthopic's large language model (LLM), Claude. As of writing this article, Anthropic released Claude v3 Sonnet and Haiku, which are, according to some statistics comparable or even better than Chat GPT v4. For this article, we will use Claude v2.1 and will not focus on which LLM is better in this article and just focus on Claude and how we can use it.</p> <h3 id="LangChain">LangChain</h3> <p>LangChain is a framework for developing applications powered by language models. It has libraries written in both Python and Javascript. For this article, we will use LangChain's Javascript library.</p> <h4 id="Agents">Agents</h4> <p>Agents are LangChain components that uses LLMs as a reasoning engine to determine which actions to take and in which order.</p> <h4 id="Tools">Tools</h4> <p>Tools are LangChain components and are functions that an Agent can invoke. Tools can be customized to fetch data from DB, call an API (which this article is about), and other custom logics to help provide a relevant response to questions and queries from User.</p> <h3 id="Monex-Insight-Market-Data-API">Monex Insight Market Data API</h3> <p>Insight is one of internal teams in Monex Securities that provides market data directly to users through it's front end systems, and to other Monex Teams and 3rd parties through Market Data APIs. For this article, we will use Scans API that returns symbols based on their Average True Ratio (ATR) and Security Type (ETF, REIT, etc).</p> <h3 id="Flow-Diagram">Flow Diagram</h3> <p>Below is a high level diagram on the flow and interaction of the components used by this sample program.</p> <pre class="code" data-lang="" data-unlink> User question ---(1)--&gt; App/Langchain ---(2)--&gt; LLM App/Langchain &lt;--(3)--- LLM App/Langchain ---(4)--&gt; Tool/Agent ---(5)--&gt; API response &lt;--(8)--- App/Langchain &lt;--(7)--- Tool/Agent &lt;--(6)--- API </pre> <p>Description of each step</p> <ol> <li>User submits a question</li> <li>App through LangChain sends the prompt that includes user's question to LLM</li> <li>LLM responds with instructions to LangChain on what will be the next action to take (e.g. call Tools/Agents) based on user question</li> <li>LangChain calls Tools/Agent</li> <li>Tool/Agent calls API, passing necessary parameters to API</li> <li>API responds with data</li> <li>Tool/Agent pass the API response data back to LangChain</li> <li>App returns the data from LangChain to User</li> </ol> <h2 id="Implementation">Implementation</h2> <h3 id="Langchains-Amazon-Bedrock-module">Langchain's Amazon Bedrock module</h3> <p>To use Amazon Bedrock in LangChain, import the Bedrock package included in LangChain, and create an instance of Bedrock that uses Anthropic Claude model.</p> <pre class="code" data-lang="" data-unlink>import { Bedrock } from &#39;langchain/llms/bedrock&#39;; const model = new Bedrock({ model: &#39;anthropic.claude-v2:1&#39;, region: &#39;ap-northeast-1&#39;, modelKwargs: { max_tokens_to_sample: 4096 }, maxTokens: 40000 // set max token to some reasonably high value where we can pass the below prompt (plus chain of thoughts) to the LLM });</pre> <p>Note: <code>modelKwargs: { max_tokens_to_sample: 4096 },</code> is added to avoid getting <code>Too many requests</code> error from Bedrock.</p> <h3 id="Prompt">Prompt</h3> <p>Create a prompt that provides detailed instruction to LLM to define its behavior on how it should respond based on use case or service we want to provide.</p> <pre class="code" data-lang="" data-unlink>const prefix = `You are a Stock Market financial assistant, who can help answer stock market related question. Please make sure you complete the above objective with the following rules: 1/ You should use the provided tools to get information about the question. 2/ Answer ONLY based on the response from these tools. DO NOT make up answers. When your task is complete, set &#39;Final Answer:&#39; field with the formatted response, nothing else. DO NOT truncate response.`;</pre> <pre class="code" data-lang="" data-unlink>const suffix = `Begin! Human: {input} {agent_scratchpad} Assistant:`; const createPromptArgs = { suffix, prefix, inputVariables: [&#39;input&#39;, &#39;agent_scratchpad&#39;], }; const prompt = ZeroShotAgent.createPrompt(tools, createPromptArgs);</pre> <p>AWS and Anthropic provides guidelines on how to do prompt engineering (see <a href="https://docs.anthropic.com/claude/docs/introduction-to-prompt-design">References</a>) but from this sample prompt: - Claude prompt expects <code>Human</code> and <code>Assistance</code> to be included in the prompt. If those are missing, Clause will return bad request response.</p> <pre class="code" data-lang="" data-unlink>Human: {input} Assistance:</pre> <ul> <li>Instruct the LLM to use only the tools provided</li> <li>Instruct the LLM to base its responses to queries only based on the data from the Agent/Tools and not make up answers. This is to avoid any hallucinations.</li> </ul> <h3 id="Chains">Chains</h3> <p>Chains help make a sequence of calls to LLM or our custom tools. To initialize, we pass the LLM model to use (which is Bedrock), and the prompt.</p> <pre class="code" data-lang="" data-unlink>const llmChain = new LLMChain({ llm: model, prompt });</pre> <h3 id="Agents-and-Tools">Agents and Tools</h3> <p>Next, define the agent and our custom tools which the Chain can call if needed.</p> <h4 id="Sample-Tool">Sample Tool</h4> <p>Below is a sample code on how to define custom tool that calls an API. More about this can be found in <a href="https://js.langchain.com/docs/modules/agents/tools/dynamic">References</a>.</p> <pre class="code" data-lang="" data-unlink>const securityType = new DynamicTool({ name: &#39;security-type&#39;, returnDirect: true, // set to true so that llm chain will stop once it receives response from this tool. // If this is set to false, the llm chain will continue to run as it cannot determinate some terminal state. description: &#34;Useful when getting the list of symbols with their Security Type such as &#39;ETF&#39;, &#39;REIT&#39;, or &#39;Normal&#39; stock. Inputs should be a JSON formatted string that has optional &#39;securityTypes&#39; array property, which when provided will return symbols that matches Security Types specified. Output, in JSON format, is an array of symbols and their Security Type.&#34;, func: async (inputAsJsonString) =&gt; { console.log(&#39;SECURITY TYPE TOOL input&#39;, inputAsJsonString); console.log(`Fetching symbols with their security type...`); let result = await fetch(); const input = JSON.parse(inputAsJsonString); const allowedTypes = input.securityTypes ?? []; // transform security type codes into human readable result = transform(result); if (allowedTypes.length &gt; 0) { // filter out symbols that does not match allowed security types result = result.filter((item) =&gt; allowedTypes.includes(item.value)); } const markdown = formatJsonToMarkdown(result, [&#39;symbol&#39;, &#39;value&#39;]); return markdown; } });</pre> <h4 id="Agent">Agent</h4> <pre class="code" data-lang="" data-unlink>import securityType from &#39;./scans/securityType.js&#39;; const tools = [securityType]; ... const agent = new ZeroShotAgent({ llmChain, allowedTools: [&#39;security-type&#39;], verbose: VERBOSE }); const agentExecutor = AgentExecutor.fromAgentAndTools({ agent, tools, verbose: VERBOSE, maxIterations: 3, returnIntermediateSteps: VERBOSE, handleParsingErrors: true });</pre> <h3 id="Pass-user-input-to-LangChain">Pass user input to LangChain</h3> <p>The basic application ready to accept user input. Pass the user input to LangChain via the agent executor that was created earlier. Based on user input, and through LLM, LangChain will get instructions on what will be the next action to take.</p> <pre class="code" data-lang="" data-unlink>const result = await agentExecutor.invoke({ input: line });</pre> <p>Sample input:</p> <pre class="code" data-lang="" data-unlink>QUESTION&gt; get all symbols with Security Types ETF and REIT</pre> <p>Below is a log output that shows the interaction between LangChain and LLM based based on user's input:</p> <pre class="code" data-lang="" data-unlink>[chain/start] [1:chain:AgentExecutor] Entering Chain run with input: { &#34;input&#34;: &#34;get all symbols with Security Types ETF and REIT&#34; } [chain/start] [1:chain:AgentExecutor &gt; 2:chain:LLMChain] Entering Chain run with input: { &#34;input&#34;: &#34;get all symbols with Security Types ETF and REIT&#34;, &#34;agent_scratchpad&#34;: &#34;&#34;, &#34;stop&#34;: [ &#34;\nObservation: &#34; ] } [llm/start] [1:chain:AgentExecutor &gt; 2:chain:LLMChain &gt; 3:llm:Bedrock] Entering LLM run with input: { &#34;prompts&#34;: [ &#34;You are a Stock Market financial assistant, who can help answer stock market related question. \n\nPlease make sure you complete the above objective with the following rules:\n1/ You should use the provided tools to get information about the question.\n2/ Answer ONLY based on the response from these tools. DO NOT make up answers.\n\nWhen your task is complete, set &#39;Final Answer:&#39; field with the formatted response, nothing else. DO NOT truncate response.\n\natr: Useful when getting the ATR or Average True Range values of symbols. Inputs should be a JSON formatted string that has optional &#39;minimum&#39; and &#39;maximum&#39; properties, which when provided will return symbols with ATR values between those minimum and maximum values. Output, in JSON format, is an array of symbols and their ATR values in percent unit.\nsecurity-type: Useful when getting the list of symbols with their Security Type such as &#39;ETF&#39;, &#39;REIT&#39;, or &#39;Normal&#39; stock. Inputs should be a JSON formatted string that has optional &#39;securityTypes&#39; array property, which when provided will return symbols that matches Security Types specified. Output, in JSON format, is an array of symbols and their Security Type.\n\nUse the following format in your response:\n\nQuestion: the input question you must answer\nThought: you should always think about what to do\nAction: the action to take, should be one of [atr,security-type]\nAction Input: the input to the action\nObservation: the result of the action\n... (this Thought/Action/Action Input/Observation can repeat N times)\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n\nBegin!\n\nHuman: get all symbols with Security Types ETF and REIT\n\n\n\nAssistant:&#34; ] } [llm/end] [1:chain:AgentExecutor &gt; 2:chain:LLMChain &gt; 3:llm:Bedrock] [3.71s] Exiting LLM run with output: { &#34;generations&#34;: [ [ { &#34;text&#34;: &#34; Question: get all symbols with Security Types ETF and REIT\n\nThought: To get a list of symbols filtered by security type, I should use the security-type tool. I need to specify ETF and REIT as the securityTypes filter.\n\nAction: security-type\nAction Input: {\&#34;securityTypes\&#34;:[\&#34;ETF\&#34;,\&#34;REIT\&#34;]}\n&#34; } ] ] } [chain/end] [1:chain:AgentExecutor &gt; 2:chain:LLMChain] [3.71s] Exiting Chain run with output: { &#34;text&#34;: &#34; Question: get all symbols with Security Types ETF and REIT\n\nThought: To get a list of symbols filtered by security type, I should use the security-type tool. I need to specify ETF and REIT as the securityTypes filter.\n\nAction: security-type\nAction Input: {\&#34;securityTypes\&#34;:[\&#34;ETF\&#34;,\&#34;REIT\&#34;]}\n&#34; } </pre> <p>As shown in the above LLM's output, LLM is telling LangChain that the next action is to execute tool called <code>security-type</code> and the input to the tool is <code>{"securityTypes":["ETF","REIT"]}</code></p> <pre class="code" data-lang="" data-unlink>[agent/action] [1:chain:AgentExecutor] Agent selected action: { &#34;tool&#34;: &#34;security-type&#34;, &#34;toolInput&#34;: &#34;{\&#34;securityTypes\&#34;:[\&#34;ETF\&#34;,\&#34;REIT\&#34;]}&#34;, &#34;log&#34;: &#34; Question: get all symbols with Security Types ETF and REIT\n\nThought: To get a list of symbols filtered by security type, I should use the security-type tool. I need to specify ETF and REIT as the securityTypes filter.\n\nAction: security-type\nAction Input: {\&#34;securityTypes\&#34;:[\&#34;ETF\&#34;,\&#34;REIT\&#34;]}\n&#34; } [tool/start] [1:chain:AgentExecutor &gt; 4:tool:security-type] Entering Tool run with input: &#34;{&#34;securityTypes&#34;:[&#34;ETF&#34;,&#34;REIT&#34;]}&#34;</pre> <p>The above log shows that Agent runs the tool <code>security-type</code> passing the arguments as provided by the LLM.</p> <pre class="code" data-lang="" data-unlink>SECURITY TYPE TOOL input {&#34;securityTypes&#34;:[&#34;ETF&#34;,&#34;REIT&#34;]} Fetching symbols with their security type... &gt;&gt;INSIGHT: Fetching Security Types from https://api.monexinsight.net/market/scans/SecurityType [tool/end] [1:chain:AgentExecutor &gt; 4:tool:security-type] [348ms] Exiting Tool run with output: &#34;| symbol | value | | --- | --- | | JP:1305-TS | ETF | | JP:1306-TS | ETF | | JP:1308-TS | ETF | | JP:1309-TS | ETF | | JP:1311-TS | ETF | | JP:1319-TS | ETF | | JP:1320-TS | ETF | | JP:1321-TS | ETF | ... [chain/end] [1:chain:AgentExecutor] [4.06s] Exiting Chain run with output: { &#34;output&#34;: &#34;| symbol | value |\n| --- | --- |\n| JP:1305-TS | ETF |\n| JP:1306-TS | ETF |\n| JP:1308-TS | ETF |\n| JP:1309-TS | ETF |\n| JP:1311-TS | ETF |\n| JP:1319-TS | ETF |\n| JP:1320-TS | ETF |\n| JP:1321-TS | ETF |\n| JP:1322-TS | ETF |\n| JP:1325-TS | ETF |\n| JP:1328-TS | ETF |\n| JP:1329-TS | ETF |\n| JP:1330-TS | ETF |\n| JP:133A-TS | ETF |\n| JP:1343-TS | ETF |\n| JP:1345-TS | ETF |\n| JP:1346-TS | ETF |\n| JP:1348-TS | ETF |\n| JP:1356-TS | ETF |\n| JP:1357-TS | ETF |\n| JP:1358-TS | ETF ... &#34;intermediateSteps&#34;: [ { &#34;action&#34;: { &#34;tool&#34;: &#34;security-type&#34;, &#34;toolInput&#34;: &#34;{\&#34;securityTypes\&#34;:[\&#34;ETF\&#34;,\&#34;REIT\&#34;]}&#34;, &#34;log&#34;: &#34; Question: get all symbols with Security Types ETF and REIT\n\nThought: To get a list of symbols filtered by security type, I should use the security-type tool. I need to specify ETF and REIT as the securityTypes filter.\n\nAction: security-type\nAction Input: {\&#34;securityTypes\&#34;:[\&#34;ETF\&#34;,\&#34;REIT\&#34;]}\n&#34; }, &#34;observation&#34;: &#34;| symbol | value |\n| --- | --- |\n| JP:1305-TS | ETF |\n| JP:1306-TS | ETF |\n| JP:1308-TS | ETF |\n| JP:1309-TS | ETF |\n| JP:1311-TS | ETF |\n| JP:1319-TS | ETF |\n| JP:1320-TS | ETF |\n| JP:1321-TS | ETF |\n| JP:1322-TS | ETF |\n| JP:1325-TS | ETF |\n| JP:1328-TS | ETF |\n| JP:1329-TS | ETF |\n| JP:1330-TS | ETF |\n| JP:133A-TS | ... </pre> <p>The above log shows the actual tool has been invoked and API returned some data. The program then shows the API response back to user, which is something similar as shown below.</p> <pre class="code" data-lang="" data-unlink>ANSWER&gt;&gt; | symbol | value | | --- | --- | | JP:1305-TS | ETF | | JP:1306-TS | ETF | | JP:1308-TS | ETF | | JP:1309-TS | ETF | | JP:1311-TS | ETF | | JP:1319-TS | ETF | ... | JP:2971-TS | REIT | | JP:2972-TS | REIT | | JP:2979-TS | REIT | | JP:2989-TS | REIT | | JP:3226-TS | REIT | | JP:3234-TS | REIT |</pre> <h2 id="Summary">Summary</h2> <p>The above sample program shows that through frameworks such as LangChain, and LLMs such as Amazon Bedrock and Anthropic Claude, we can create tools that can provide data by calling APIs, Data Sources or execute proprietary logic/algorithm and return data to users based on user input.</p> <h2 id="References">References</h2> <ul> <li><a href="https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html">What is Amazon Bedrock</a></li> <li><a href="https://aws.amazon.com/bedrock/claude/">Anthropic Claude on Amazon Bedrock</a></li> <li><a href="https://docs.anthropic.com/claude/docs/introduction-to-prompt-design">Intro to prompt</a></li> <li><a href="https://docs.anthropic.com/claude/docs/optimizing-your-prompt">Prompt engineering</a></li> <li><a href="https://js.langchain.com/docs/get_started/introduction">LangChain.js</a></li> <li><a href="https://js.langchain.com/docs/modules/agents/tools/dynamic">Defining custom tools</a></li> </ul> <p><br></p> <p>Monex Insight Senior Engineer<br> Jay Landicho</p> nzkkun ChatGPTをより効率的に活用する方法ープロンプトエンジニアリング hatenablog://entry/6801883189085209036 2024-03-01T11:01:15+09:00 2024-03-01T11:01:15+09:00 こんにちは。システム開発一部の朴です。 ChatGPTが世の中に公開されたから約1年が経ちまして、日常生活にすみずみと染み込んでいることがますます感じる毎日です。その中で、今回はChatGPTをより効率的に活用する方法の、プロンプトエンジニアリングについて話してみたいと思います。 Purdue大学で実施したChatGPTに関する最近の研究によりますと、Stack Overflow*1(以降SOと称します)に実際存在する517個のコーディング質問をChatGPTに投げた時に、52%の場合は不正確か、動作できないコードを答えとして提供したということでした。 *2 このグラフではSOの答えとChat… <p style="text-align: left;">こんにちは。システム開発一部の朴です。 </p> <p>ChatGPTが世の中に公開されたから約1年が経ちまして、日常生活にすみずみと染み込んでいることがますます感じる毎日です。その中で、今回はChatGPTをより効率的に活用する方法の、プロンプトエンジニアリングについて話してみたいと思います。</p> <p> </p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eonha_park/20240222/20240222125318.png" width="225" height="225" loading="lazy" title="" class="hatena-fotolife" itemprop="image" style="display: block; margin-left: auto; margin-right: auto;" /></p> <p> </p> <p>Purdue大学で実施したChatGPTに関する最近の研究によりますと、Stack Overflow<a href="#f-7e491932" id="fn-7e491932" name="fn-7e491932" title="世界大規模の開発者コミュニティ">*1</a>(以降SOと称します)に実際存在する517個のコーディング質問をChatGPTに投げた時に、52%の場合は不正確か、動作できないコードを答えとして提供したということでした。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eonha_park/20240222/20240222130701.png" width="580" height="371" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /><a href="#f-83682f05" id="fn-83682f05" name="fn-83682f05" title="Who Answers It Better? An In-Depth Analysis of ChatGPT and Stack Overflow Answers to Software Engineering Qestions - Purdue Universityにより">*2</a></p> <p>このグラフではSOの答えとChatGPTの答えを比較していますが、ChatGPTはSOより、正確性、網羅性、簡潔性、有用性の四つの項目で低い点数を記録したそうです。</p> <p>他の問題としては、答えが半分だけ合ったり、完全に間違ったにもかかわらず、自信を持った言葉で説得力のある答えを作り上げたことが分かったことです。</p> <p>たまに、ChatGPTはまるで幻を見ているように何かを作り上げるため、ChatGPTが出す全ての答えを信用することはできなく、質問をする際にある程度、質問について理解をしていないと、ChatGPTが出した答えを評価できないので注意が必要というのが、論文の結論でした。</p> <p>ということで、検索をうまくしないと良質な検索結果が得られないグーグルのように、ChatGPTもより効率的な、正確な答えを得られるためには、ChatGPTに効率的に質問をする方法を身に付ける必要性があることに注目されました。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eonha_park/20240222/20240222131509.png" width="421" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /><a href="#f-c3be7cd9" id="fn-c3be7cd9" name="fn-c3be7cd9" title="A Prompt Pattern Catalog to Enhance Prompt Engineering with ChatGPT - Vanderbilt University より">*3</a></p> <p>Vanderbilt大学の計算機工学者たちが実施したChatGPTからより良い答えを得られると立証された、様々なプロンプトのパターンが含まれた論文を発表しました。この論文にはChatGPTから最良な結果を得られるプロンプトのパターンを紹介しています。</p> <h3 id="ペルソナパターンPersona-Pattern">ペルソナパターン<br />Persona Pattern</h3> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eonha_park/20240222/20240222132206.png" width="823" height="338" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>このパターンを使うと、ChatGPTが作り上げる答えの種類や、集中してほしい細かい事項について案内することができます。このパターンを使うと、特定な観点や見解を一貫して貫くようにChatGPTをカスタマイズできると言われています。</p> <p>例としてソースコードをレビューしたい場合は、ChatGPT次のように聞きます。</p> <blockquote> <p>あなたは、グーグルのシニアエンジニアです。セキュリティー観点で次のソースコードをレビューしてください。シニアエンジニアとしてのソースコードも提供してください。</p> </blockquote> <p>このパターンを使うと、ChatGPTがより良い結果を出すために、集中する対象を絞ることができる。このパターンを使うと、確実にChatGPTの答えの品質が向上し、質問者が投げた質問のテーマだけにChatGPTが語ることができるといわれています。</p> <h3 id="レシピパターンRecipe-Pattern">レシピパターン<br />Recipe Pattern</h3> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eonha_park/20240222/20240222132444.png" width="596" height="465" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>このパターンは達成したい目標があり、材料も知っていて、達成するための手順もある程度分かっているが、これらを全てまとめるのが分からない場合に役に立つといわれています。</p> <p>このパターンは特にプログラマたちに役に立って、多くの時間を節約することができ、ソースコードを理解するのにとても役に立つといわれています。</p> <blockquote> <p>データを暗号化するJavaプログラムを作ろうとしています。私は引数を読んで、バリデーションチェックを実施し、暗号化し、暗号化したデータを返すことが必要なのを分かっています。このプログラムが動作するために必要な全体的な流れと、漏れている個所があれば詰めて、不要なところがないか教えてください。</p> </blockquote> <p>「漏れている個所があれば詰めてください」という文章は、特に我々が漏れている個所があっても、後続の質問をしないでChatGPTが補うことができますし、「不要なところがないか教えてください」という文章は、よりよいレシピを作るためにChatGPTを不正確な部分を見つけるよう指示することができるため、役に立つといわれています。</p> <h3 id="反射パターンReflection-Pattern">反射パターン<br />Reflection Pattern</h3> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eonha_park/20240222/20240222132710.png" width="705" height="478" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>このパターンは全ての答えについて理由を説明するようChatGPTに指示することができます。</p> <p>これは答えをよりスムーズに理解するのに役に立って、ChatGPTが幻を見て間違った答えを出すことを防ぐこができます。</p> <blockquote> <p>答えする際には、その答えの根拠と、仮定を説明してください。選定した答えについて潜在している制限事項や、代表的なケースを説明してください。</p> </blockquote> <p>このパターンを使うと、答えがより詳細になり、ChatGPTが答えについての背景情報を普段よりより多く提供するといわれています。</p> <h3 id="拒否遮断パターンRefusal-Breaker-Pattern">拒否遮断パターン<br />Refusal Breaker Pattern</h3> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eonha_park/20240222/20240222132849.png" width="776" height="512" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>このパターンは知識制限、安全などの理由でChatGPTが答えられない場合に役に立つパターンです。聞きたい質問の文章を変えたり、再構成してChatGPTから応えられるように誘導するパターンとなります。</p> <blockquote> <p>質問に答えられない理由を説明してください。答え出来る質問の代替表現を一つ以上提供してください。</p> </blockquote> <p>この文章で、ChatGPTが答えられない場合は、本来の質問に代替できる質問がいくつか表示され、結果的にはChatGPTから答えを得られるといわれています。</p> <h3 id="ひっくり返した相互作用パターンFlipped-Interaction-Pattern">ひっくり返した相互作用パターン<br />Flipped Interaction Pattern</h3> <p><img src="https://img.freepik.com/free-vector/social-interaction-concept-illustration_114360-4864.jpg" alt="Free Vector | Social interaction concept illustration" /></p> <p>たまにChatGPTに質問する時に、質問をどう構成して、どれだけ必要な情報をあげればよいか悩んだことありませんか?その時にこのパターンはとても役になります。</p> <p>このパターンはスクリプトをひっくり返して、我々がほしいものを得られるようにChatGPTが我々に聞くように指示することができます。欲しい結果が分かっているけど、その目標を達成するための手順が分からなかったり、その目標を達成するためにChatGPTがどの情報を必要とするかを分からない場合に役に立つといわれています。</p> <blockquote> <p>AWSにあるウェブサーバーにJavaのアプリをデプロイする方法について私に質問してください。必要な全ての情報を得たらデプロイを自動化するスクリプトを作成してください。</p> </blockquote> <p>このクェリを投げますと、ChatGPTからユーザーに必要な情報について色々聞いてきます。我々がすることとしては、聞かれた質問に答えるだけで、JavaのアプリをAWSに自動的にデプロイできるスクリプトを得られることができます。もちろん、会社の情報だと絶対禁止ですが、個人用だととても有益だなと思いました。</p> <p>このパターンは必要な情報と手順などを我々がChatGPTに聞く前に調べるより、とても便利で、我々が会話をリードする代わりに、ChatGPTが会話をリードするようにして、可能な限り少ないメッセージと、相互作用で必要な情報を的確に得られることができるといわれています。</p> <p> </p> <h3 id="まとめ">まとめ</h3> <p>今まで様々なプロンプトパターンについて、紹介させていただきました。ChatGPTに興味ある方がいましたら、ぜひお試し下さい!ご参考になれば何よりです。弊社でも最近、ChatGPTを用いた業務改善に力を注いでいます。今回学んだ色々なプロンプトパターンを活用していきたいと思います。</p> <p>では、また!</p> <p> </p><div class="footnote"> <p class="footnote"><a href="#fn-7e491932" id="f-7e491932" name="f-7e491932" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">世界大規模の開発者コミュニティ</span></p> <p class="footnote"><a href="#fn-83682f05" id="f-83682f05" name="f-83682f05" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">Who Answers It Better? An In-Depth Analysis of ChatGPT and Stack Overflow Answers to Software Engineering Qestions - Purdue Universityにより</span></p> <p class="footnote"><a href="#fn-c3be7cd9" id="f-c3be7cd9" name="f-c3be7cd9" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">A Prompt Pattern Catalog to Enhance Prompt Engineering with ChatGPT - Vanderbilt University より</span></p> </div> eonha_park MongoDBに悩む(前編) hatenablog://entry/6801883189083098682 2024-02-16T17:42:23+09:00 2024-02-16T17:42:23+09:00 こんにちは マネックスラボのMです。 ラボでは過去に構築されたシステムにMongoDBを使用しています どうやらその当時流行っていたという理由で採用されたようですが ラボのサービスへのアクセスが増えてきたことで最近は限界を感じています 実はまだ解決に至っていないのですが、経過報告をさせていただきます Insert系が遅い ラボのあるサービスではかなり高頻度でInsertされ、高頻度でSelectされる使い方をしています Insertされる処理をA、Selectされる処理をBとしたとき AとB両方に秒間155回ずつ1分間 合計18600回の処理を実行すると 以下に表されるように処理時間が遅くなっ… <p>こんにちは マネックスラボのMです。</p> <p>ラボでは過去に構築されたシステムにMongoDBを使用しています<br/> どうやらその当時流行っていたという理由で採用されたようですが<br/> ラボのサービスへのアクセスが増えてきたことで最近は限界を感じています<br/> 実はまだ解決に至っていないのですが、経過報告をさせていただきます</p> <h2 id="Insert系が遅い">Insert系が遅い</h2> <p>ラボのあるサービスではかなり高頻度でInsertされ、高頻度でSelectされる使い方をしています<br/> Insertされる処理をA、Selectされる処理をBとしたとき<br/> AとB両方に秒間155回ずつ1分間 合計18600回の処理を実行すると<br/> 以下に表されるように処理時間が遅くなったりエラーになってしまいます<br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/M/MNXM/20240214/20240214135823.png" width="478" height="333" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ちなみにAとB個別に処理時間の遅延が発生する限界を調べると<br/> Aは秒間210程度の処理で限界を迎えますが<br/> Bは秒間500程度の処理まで耐えることができます<br/> 約2.3倍の処理能力の差があることになります<br/> これはMongoDBがレプリケーションを前提とした仕組みであるため<br/> Aにはレプリケーションによる処理遅延が発生しているのではないかと想像しました</p> <h2 id="レプリカセットをゼロにしてみる">レプリカセットをゼロにしてみる</h2> <pre class="code" data-lang="" data-unlink>rs.remove(&#34;mongo02.monex.co.jp:11111&#34;); rs.remove(&#34;mongo03.monex.co.jp:11111&#34;);</pre> <p>とりあえず手軽に検証できるので、強制的に1台になるようにしてみました<br/> すると、劇的にパフォーマンスが改善しました<br/> <figure class="figure-image figure-image-fotolife" title="レプリカセット0 1回目"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/M/MNXM/20240214/20240214134114.png" width="476" height="322" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>レプリカセット0 1回目</figcaption></figure></p> <p>が、複数回負荷をかけてみると、どんどんとパフォーマンスが劣化していきます<br/> ※そもそもMongoDBは最低1つのレプリカセットを必須にしているらしいので、的外れなことをやっているかもしれませんが・・ <figure class="figure-image figure-image-fotolife" title="レプリカセット0 2回目"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/M/MNXM/20240214/20240214134340.png" width="479" height="324" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>レプリカセット0 2回目</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="レプリカセット0 3回目"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/M/MNXM/20240214/20240214134504.png" width="472" height="320" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>レプリカセット0 3回目</figcaption></figure></p> <h2 id="レプリカセットを3つに戻してみる">レプリカセットを3つに戻してみる</h2> <pre class="code" data-lang="" data-unlink>rs.add(&#34;mongo02.monex.co.jp:11111&#34;); rs.add(&#34;mongo03.monex.co.jp:11111&#34;);</pre> <p>すると、ゼロにしたときと同様にまた劇的にパフォーマンスが改善しました<br/> <figure class="figure-image figure-image-fotolife" title="レプリカセット3 1回目"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/M/MNXM/20240214/20240214134615.png" width="471" height="319" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>レプリカセット3 1回目</figcaption></figure></p> <p>そして、複数回負荷をかけてみると、これまた同様にどんどんとパフォーマンスが劣化していきます<br/> なんだったら、レプリカセット0よりもパフォーマンスが良く、2回目と3回目の劣化度合いもマシです <figure class="figure-image figure-image-fotolife" title="レプリカセット3 2回目"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/M/MNXM/20240214/20240214134812.png" width="471" height="320" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>レプリカセット3 2回目</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="レプリカセット3 3回目"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/M/MNXM/20240214/20240214134915.png" width="471" height="319" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>レプリカセット3 3回目</figcaption></figure></p> <p>が、これは何度も試した感じではたまたまこうなっただけで誤差の範囲です<br/> つまり、レプリカセットの数に限らず、レプリカセットの設定を変更した際に<br/> 何かしらの変化が現れている可能性が高いという予想をしています</p> <h2 id="そもそも使っているMongoDBが古すぎる">そもそも使っているMongoDBが古すぎる</h2> <p>実はラボがこのシステムで使用しているMongoDBのバージョンは2.6.12です<br/> 現在の最新バージョンは7ですから、チューニング以前にまずはバージョンアップするのが自然だと思っています<br/> バージョンアップで解決するのか、更にチューニングが必要なのか、はたまたMongoDBとお別れするのか<br/> 次回、後編でまた報告させていただきます</p> MNXM CDKでAPI GatewayのアクセスログをAthenaで検索する仕組みを作る hatenablog://entry/6801883189078917712 2024-02-02T12:00:00+09:00 2024-02-13T07:20:15+09:00 CDKを使用して、API GatewayのアクセスログをAthenaで分析する仕組みを構築します。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20240126/20240126155230.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>マネックス証券 システム管理部のHです。<br></p> <h3 id="はじめに">はじめに</h3> <p>あるとき、なんとなくAmazon API Gatewayでパスごとの呼び出し回数をカウントしてみたいと思いました。これができればどの機能が多く利用されているかを分析できるかなと。またバックエンドの負荷の遷移をAPI呼び出しの観点から分析することもできます。特定の時間帯に負荷が偏っているような場合には、その時間帯に呼ばれるAPIを調べることでバックエンドで負荷をかけている処理を特定することもできます。</p> <h3 id="やりたいこと">やりたいこと</h3> <p>やり方はいくつかあると思いますが、今回はAPI GatewayのアクセスログをAmazon Kinesis Data Firehose経由でS3に出力し、それをAmazon Athenaでクエリを使用して検索する方法にします。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20240127/20240127164503.png" width="792" height="321" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="実装してみる">実装してみる</h3> <p>今回はAPI Gatewayの作成からAthenaのテーブル作成まで一通り<a href="https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/home.html">CDK</a>で実装します。CDKの実装はいつも通りJavaでやります。<br> CDKでは以下のリソースを作成します。</p> <ul> <li>アクセスログ出力用S3バケット</li> <li>API Gateway</li> <li>Firehose</li> <li>Athena データベース、テーブル</li> </ul> <p>ちなみにCDKについては、<a href="https://blog.tech-monex.com/entry/2020/12/25/000000">この辺の記事</a>で説明しているので、こちらも参照ください。</p> <h4 id="S3バケットの作成">S3バケットの作成</h4> <p>アクセスログを保存するバケットを作成します。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synType">private</span> Bucket createS3Bucket() { <span class="synStatement">return</span> Bucket .Builder .create(stack, S3BUCKET_NAME) .bucketName(S3BUCKET_NAME) .removalPolicy(RemovalPolicy.DESTROY) .build(); } </pre> <h4 id="API-GatewayFirehoseのリソース作成">API Gateway、Firehoseのリソース作成</h4> <p>API Gatewayとアクセスログ出力先のFirehoseに関するリソースを作成します。</p> <h5 id="アクセスログのフォーマット設定">アクセスログのフォーマット設定</h5> <p>アクセスログにはリクエスト時間、APIのID、リクエストID、パス、HTTPメソッド、プロトコル、ステータスコードをカンマ区切りで出力します。さらにそのままでは改行されないため、最後に改行コード(\n)を追加します。 アクセスログに出力できる項目は下記が参考になります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.aws.amazon.com%2Fja_jp%2Fapigateway%2Flatest%2Fdeveloperguide%2Fapi-gateway-mapping-template-reference.html" title="API Gateway マッピングテンプレートとアクセスのログ記録の変数リファレンス - Amazon API Gateway" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html">docs.aws.amazon.com</a></cite></p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synType">private</span> AccessLogFormat createAccessLogFormat() { <span class="synComment">// アクセスログの設定。CSV形式にする。</span> String logFormatStr = AccessLogField.contextRequestTime() + <span class="synConstant">&quot;,&quot;</span> + AccessLogField.contextApiId() + <span class="synConstant">&quot;,&quot;</span> + AccessLogField.contextRequestId() + <span class="synConstant">&quot;,&quot;</span> + AccessLogField.contextResourcePath() + <span class="synConstant">&quot;,&quot;</span> + AccessLogField.contextHttpMethod() + <span class="synConstant">&quot;,&quot;</span> + AccessLogField.contextProtocol() + <span class="synConstant">&quot;,&quot;</span> + AccessLogField.contextStatus() + <span class="synConstant">&quot;</span><span class="synSpecial">\n</span><span class="synConstant">&quot;</span>; <span class="synStatement">return</span> AccessLogFormat.custom(logFormatStr); } </pre> <h5 id="Firehoseリソースの作成">Firehoseリソースの作成</h5> <p>Firehoseに設定するロールとFirehoseの配信ストリームを作成します。ロールにはS3に対するアクセス許可を設定します。<br> 配信ストリームを作成するときには1つ注意が必要で、ストリーム名は「amazon-apigateway」で始まる必要があります。<br> ちなみに<a href="https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/home.html">CDKのAPIリファレンス</a>はサンプルコードが書いてあるので、かなり参考になります。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synType">private</span> IAccessLogDestination createLogDestination() { <span class="synComment">// S3バケットを作成</span> Bucket bucket = createS3Bucket(); <span class="synComment">// Firehose用のロールを作成</span> Role firehoseRole = Role.Builder.create(stack, <span class="synConstant">&quot;firehoseRole&quot;</span>) .assumedBy(<span class="synStatement">new</span> ServicePrincipal(<span class="synConstant">&quot;firehose.amazonaws.com&quot;</span>)) .inlinePolicies(Map.of(<span class="synConstant">&quot;firehosePolicy&quot;</span>, PolicyDocument.Builder.create() .statements(List.of(PolicyStatement.Builder.create() .effect(Effect.ALLOW) .actions(List.of( <span class="synConstant">&quot;s3:AbortMultipartUpload&quot;</span>, <span class="synConstant">&quot;s3:GetBucketLocation&quot;</span>, <span class="synConstant">&quot;s3:GetObject&quot;</span>, <span class="synConstant">&quot;s3:ListBucket&quot;</span>, <span class="synConstant">&quot;s3:ListBucketMultipartUploads&quot;</span>, <span class="synConstant">&quot;s3:PutObject&quot;</span>)) .resources(List.of( bucket.getBucketArn(), bucket.getBucketArn() + <span class="synConstant">&quot;/*&quot;</span>)) .build())) .build())) .build(); <span class="synComment">// Firehose配信ストリーム作成</span> CfnDeliveryStream firehoseStream = CfnDeliveryStream.Builder.create(stack, <span class="synConstant">&quot;firehoseStream&quot;</span>) .deliveryStreamName(<span class="synConstant">&quot;amazon-apigateway-eblog202401-stream&quot;</span>) .s3DestinationConfiguration(S3DestinationConfigurationProperty.builder() .bucketArn(bucket.getBucketArn()) .roleArn(firehoseRole.getRoleArn()) .prefix(S3PREFIX_NAME) .bufferingHints(BufferingHintsProperty.builder() .intervalInSeconds(<span class="synConstant">30</span>) .build()) .build()) .build(); <span class="synStatement">return</span> <span class="synStatement">new</span> FirehoseLogDestination(firehoseStream); } </pre> <h5 id="API-Gatewayリソースの作成">API Gatewayリソースの作成</h5> <p>確認用のREST APIを一つ作り、その下にパスを2つ、それぞれGETメソッドを作成します。中身はモックで固定レスポンスを返します。<br></p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synType">private</span> <span class="synType">void</span> createApiGateway() { String apiName = <span class="synConstant">&quot;Eblog202401&quot;</span>; <span class="synComment">// Rest APIを作成</span> RestApi api = RestApi.Builder.create(stack, apiName) .cloudWatchRole(<span class="synConstant">true</span>) .cloudWatchRoleRemovalPolicy(RemovalPolicy.DESTROY) .endpointConfiguration(EndpointConfiguration.builder() .types(List.of(EndpointType.REGIONAL)) .build()) .deploy(<span class="synConstant">true</span>) .deployOptions(StageOptions.builder() .accessLogFormat(createAccessLogFormat()) <span class="synComment">// アクセスログフォーマットの設定</span> .accessLogDestination(createLogDestination()) <span class="synComment">// アクセスログの出力先を設定</span> .loggingLevel(MethodLoggingLevel.INFO) .build()) .description(<span class="synConstant">&quot;エンジニアブログ 2024年1月用テストAPI&quot;</span>) .build(); <span class="synComment">// リソースとメソッド作成1</span> Resource resource = api.getRoot().addResource(<span class="synConstant">&quot;test&quot;</span>); resource.addMethod(<span class="synConstant">&quot;GET&quot;</span>, MockIntegration.Builder.create() .requestTemplates(Map.of(<span class="synConstant">&quot;application/json&quot;</span>, <span class="synConstant">&quot;{</span><span class="synSpecial">\&quot;</span><span class="synConstant">statusCode</span><span class="synSpecial">\&quot;</span><span class="synConstant">: 200}&quot;</span>)) .integrationResponses(List.of(IntegrationResponse.builder() .responseTemplates(Map.of( <span class="synConstant">&quot;application/json&quot;</span>, <span class="synConstant">&quot;&quot;&quot;</span> { <span class="synError">\</span><span class="synConstant">&quot;statusCode</span><span class="synSpecial">\&quot;</span><span class="synConstant">: 200,</span> <span class="synError">\</span><span class="synConstant">&quot;message</span><span class="synSpecial">\&quot;</span><span class="synConstant">: </span><span class="synSpecial">\&quot;</span><span class="synConstant">test is OK.</span><span class="synSpecial">\&quot;</span> } <span class="synConstant">&quot;&quot;&quot;))</span> .statusCode(<span class="synConstant">&quot;200&quot;</span>) .build())) .passthroughBehavior(PassthroughBehavior.NEVER) .build(), MethodOptions.builder() .methodResponses(List.of(MethodResponse.builder() .statusCode(<span class="synConstant">&quot;200&quot;</span>) .build())) .build()); <span class="synComment">// リソースとメソッド作成2</span> Resource resource2 = api.getRoot().addResource(<span class="synConstant">&quot;test2&quot;</span>); resource2.addMethod(<span class="synConstant">&quot;GET&quot;</span>, MockIntegration.Builder.create() .requestTemplates(Map.of(<span class="synConstant">&quot;application/json&quot;</span>, <span class="synConstant">&quot;{</span><span class="synSpecial">\&quot;</span><span class="synConstant">statusCode</span><span class="synSpecial">\&quot;</span><span class="synConstant">: 200}&quot;</span>)) .integrationResponses(List.of(IntegrationResponse.builder() .responseTemplates(Map.of( <span class="synConstant">&quot;application/json&quot;</span>, <span class="synConstant">&quot;&quot;&quot;</span> { <span class="synError">\</span><span class="synConstant">&quot;statusCode</span><span class="synSpecial">\&quot;</span><span class="synConstant">: 200,</span> <span class="synError">\</span><span class="synConstant">&quot;message</span><span class="synSpecial">\&quot;</span><span class="synConstant">: </span><span class="synSpecial">\&quot;</span><span class="synConstant">test2 is OK.</span><span class="synSpecial">\&quot;</span> } <span class="synConstant">&quot;&quot;&quot;))</span> .statusCode(<span class="synConstant">&quot;200&quot;</span>) .build())) .passthroughBehavior(PassthroughBehavior.NEVER) .build(), MethodOptions.builder() .methodResponses(List.of(MethodResponse.builder() .statusCode(<span class="synConstant">&quot;200&quot;</span>) .build())) .build()); } </pre> <h4 id="Athenaのデータベーステーブル作成">Athenaのデータベース、テーブル作成</h4> <p>Athenaとは書いていますが、実際にはAWS Glueのデータベースとテーブルを作成します。 テーブルですが、参照するS3上には「バケット名/プレフィックス/年/月/日/時」という風に階層が作成されてその下にオブジェクトが配置されるので、検索するデータのコストやパフォーマンスを考慮して<a href="https://docs.aws.amazon.com/ja_jp/athena/latest/ug/partition-projection.html">パーティション射影</a>を使用します。Javaで実装されている例があまりなかったので、やや苦労しました。<br> そんなときにはやはり<a href="https://docs.aws.amazon.com/cdk/api/v2/java/software/amazon/awscdk/services/glue/CfnTable.html">APIリファレンス</a>は参考になります。あとは<a href="https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-glue-table.html">Cloudformation定義</a>ですね。CfnTableは<a href="https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/constructs.html">CDKのコンストラクト</a>で言えばL1コンストラクトになるので、Cloudformationの定義がわかれば、そのままCDKの実装に繋げられます。</p> <pre class="code lang-java" data-lang="java" data-unlink> <span class="synType">private</span> <span class="synType">void</span> createAthena() { <span class="synComment">// データベースを作成</span> String dbName = <span class="synConstant">&quot;eblog202401-db&quot;</span>; CfnDatabase.Builder.create(stack, dbName) .catalogId(stack.getAccount()) .databaseInput(DatabaseInputProperty.builder() .name(dbName) .build()) .build(); <span class="synComment">// テーブルを作成</span> String tableName = <span class="synConstant">&quot;apigateway-accesslog&quot;</span>; CfnTable.Builder.create(stack, tableName) .catalogId(stack.getAccount()) .databaseName(dbName) .tableInput(TableInputProperty.builder() .description(<span class="synConstant">&quot;エンジニアブログ 2024年1月用テーブル&quot;</span>) .name(tableName) .tableType(<span class="synConstant">&quot;EXTERNAL_TABLE&quot;</span>) .parameters(Map.of( <span class="synConstant">&quot;projection.enabled&quot;</span>, <span class="synConstant">&quot;true&quot;</span>, <span class="synConstant">&quot;projection.datehour.type&quot;</span>, <span class="synConstant">&quot;date&quot;</span>, <span class="synConstant">&quot;projection.datehour.format&quot;</span>, <span class="synConstant">&quot;yyyy/MM/dd/HH&quot;</span>, <span class="synConstant">&quot;projection.datehour.range&quot;</span>, <span class="synConstant">&quot;2024/01/01/00,NOW&quot;</span>, <span class="synConstant">&quot;projection.datehour.interval&quot;</span>, <span class="synConstant">&quot;1&quot;</span>, <span class="synConstant">&quot;projection.datehour.interval.unit&quot;</span>, <span class="synConstant">&quot;HOURS&quot;</span>, <span class="synConstant">&quot;storage.location.template&quot;</span>, <span class="synConstant">&quot;s3://&quot;</span> + S3BUCKET_NAME + <span class="synConstant">&quot;/&quot;</span> + S3PREFIX_NAME + <span class="synConstant">&quot;${datehour}/&quot;</span> )) <span class="synComment">// パーティション射影の設定</span> .partitionKeys(List.of(ColumnProperty.builder() .name(<span class="synConstant">&quot;datehour&quot;</span>) .type(<span class="synConstant">&quot;string&quot;</span>) .build())) <span class="synComment">// パーティションキーの設定</span> .storageDescriptor(StorageDescriptorProperty.builder() <span class="synComment">// カラム設定</span> .columns(List.of(ColumnProperty.builder() .name(<span class="synConstant">&quot;request_time&quot;</span>) .type(<span class="synConstant">&quot;string&quot;</span>) .comment(<span class="synConstant">&quot;CLF 形式の要求時間 (dd/MMM/yyyy:HH:mm:ss +-hhmm)。&quot;</span>) .build(), ColumnProperty.builder() .name(<span class="synConstant">&quot;id&quot;</span>) .type(<span class="synConstant">&quot;string&quot;</span>) .comment(<span class="synConstant">&quot;API Gateway が API に割り当てる識別子。&quot;</span>) .build(), ColumnProperty.builder() .name(<span class="synConstant">&quot;request_id&quot;</span>) .type(<span class="synConstant">&quot;string&quot;</span>) .comment(<span class="synConstant">&quot;リクエストの ID。&quot;</span>) .build(), ColumnProperty.builder() .name(<span class="synConstant">&quot;path&quot;</span>) .type(<span class="synConstant">&quot;string&quot;</span>) .comment(<span class="synConstant">&quot;リクエストパス。&quot;</span>) .build(), ColumnProperty.builder() .name(<span class="synConstant">&quot;method&quot;</span>) .type(<span class="synConstant">&quot;string&quot;</span>) .comment(<span class="synConstant">&quot;使用される HTTP メソッドです。&quot;</span>) .build(), ColumnProperty.builder() .name(<span class="synConstant">&quot;protocol&quot;</span>) .type(<span class="synConstant">&quot;string&quot;</span>) .comment(<span class="synConstant">&quot;HTTP/1.1 などのリクエストプロトコル。&quot;</span>) .build(), ColumnProperty.builder() .name(<span class="synConstant">&quot;status_code&quot;</span>) .type(<span class="synConstant">&quot;int&quot;</span>) .comment(<span class="synConstant">&quot;メソッドレスポンスのステータス。&quot;</span>) .build())) .inputFormat(<span class="synConstant">&quot;org.apache.hadoop.mapred.TextInputFormat&quot;</span>) .outputFormat(<span class="synConstant">&quot;org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat&quot;</span>) .location(<span class="synConstant">&quot;s3://&quot;</span> + S3BUCKET_NAME + <span class="synConstant">&quot;/&quot;</span> + S3PREFIX_NAME) .serdeInfo(SerdeInfoProperty.builder() .serializationLibrary(<span class="synConstant">&quot;org.apache.hadoop.hive.serde2.OpenCSVSerde&quot;</span>) .parameters(Map.of(<span class="synConstant">&quot;separatorChar&quot;</span>, <span class="synConstant">&quot;,&quot;</span>, <span class="synConstant">&quot;quoteChar&quot;</span>, <span class="synConstant">&quot;</span><span class="synSpecial">\&quot;</span><span class="synConstant">&quot;</span>, <span class="synConstant">&quot;escapeChar&quot;</span>, <span class="synConstant">&quot;</span><span class="synSpecial">\\</span><span class="synConstant">&quot;</span>)) .build()) <span class="synComment">// 区切り文字(CSV)の設定</span> .build()) .build()) .build(); } </pre> <h4 id="AWS環境への反映">AWS環境への反映</h4> <p>AWS環境への反映((デプロイ)は以下のコマンドを実行します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>cdk deploy </pre> <h3 id="確認してみる">確認してみる</h3> <p>AWS環境にデプロイされていることを確認します。</p> <h4 id="API-Gateway">API Gateway</h4> <p>APIにパスが2つ作成されています。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20240127/20240127192931.png" width="1200" height="508" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="Firehose">Firehose</h4> <p>S3に送信するストリームが作成されています。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20240127/20240127193135.png" width="1200" height="386" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="Athena">Athena</h4> <p>以下のテーブルが作成されています。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20240127/20240127193832.png" width="1200" height="607" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="クエリの実行">クエリの実行</h4> <p>APIを実行して、Athenaでクエリを実行する。<br> /testに対するリクエストを3回、/test2に対するリクエストを2回実行する。</p> <ul> <li><p>全件検索<br> APIのパスやステータスコードなどが確認できます。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20240130/20240130083129.png" width="1200" height="535" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p></li> <li><p>パスごとの呼び出し回数をカウント<br> パスごとの呼び出し回数を確認できます。SQLなので、書き方を工夫すれば呼び出し回数の多いパストップ10などを表示することも可能です。</p></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20240130/20240130083239.png" width="1200" height="455" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="おわりに">おわりに</h3> <p>CDKでAthenaのテーブル作成に多少躓いたのですが、パスの呼び出し回数を確認することができました。<br> アクセスログに出力できる項目は今回取り上げた項目以外にもいろいろあるので、組み合わせればいろいろな分析が行えると思います。</p> <div style="text-align: center;"> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20200830/20200830084302.png" width="118" height="200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> </div> hamo2020 AWS SDK も楽しいです hatenablog://entry/6801883189075806048 2024-01-19T10:34:02+09:00 2024-01-19T10:34:02+09:00 こんにちは、内製開発グループの山下です。 AWS のリソース一覧を得る 以前に AWS CLI を用いた AWS リソース一覧の取得方法について投稿をしました。もう1年半になるのですね。 blog.tech-monex.com この投稿は「AWS CLI コマンドを用いて JSON 形式のリソース情報を得て、jq コマンドで整形して CSV データ形式の出力を得る」話でした。 昨日の自分は他人 さて、久しぶりに AWS リソース一覧を得ようとし、ツールをちょっと修正しようとして、困惑しました。以前に作成したスクリプト、特に jq でオブジェクトを操作しているあたり、難しくてよくわからない。。 … <figure class="figure-image figure-image-fotolife mceNonEditable" title="Keyboard"> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/toshio_yamashita/20230808/20230808181652.jpg" width="640" height="427" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> </figure> <p><br />こんにちは、内製開発グループの山下です。</p> <h4 id="AWS-のリソース一覧を得る">AWS のリソース一覧を得る</h4> <p>以前に AWS CLI を用いた AWS リソース一覧の取得方法について投稿をしました。もう1年半になるのですね。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2022%2F07%2F29%2F120319" title="AWS CLI も楽しいです - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2022/07/29/120319">blog.tech-monex.com</a></cite></p> <p>この投稿は「AWS CLI コマンドを用いて JSON 形式のリソース情報を得て、jq コマンドで整形して CSV データ形式の出力を得る」話でした。</p> <h4 id="昨日の自分は他人">昨日の自分は他人</h4> <p>さて、久しぶりに AWS リソース一覧を得ようとし、ツールをちょっと修正しようとして、困惑しました。以前に作成したスクリプト、特に jq でオブジェクトを操作しているあたり、難しくてよくわからない。。</p> <p>記憶とは薄れていくものです。老いるとそのスピードは加速していきます。。</p> <p>開発中は簡単なものから、だんだん複雑なものに育てていきます。その過程をわかったうえで開発作業を継続しているので、全体が見渡せ、なかなかのスピードでコードを書いていけます。でも時間が経つと、その過程の肌感覚は失われてしまう。</p> <p>シェルスクリプト、そして jq でガリガリ整形処理するコードは短くて楽しいけれど、保守性が低くてメンテが難しいよね、もう細かいとこ忘れちゃったよ、辛いよ、ということです。。</p> <h4 id="コードは最高のドキュメントかもしれない">コードは最高のドキュメントかもしれない</h4> <p>シェルスクリプト処理を、まともな開発言語を用いて書き直すのは定番の対応です。なので今回は、NodeJS 環境の JavaScript (TypeScript) 言語を用いて、AWS SDK を用いてリソース一覧を得るツールとして書き直すことにしました。</p> <p>JavaScript (TypeScript)  は JSON 形式のデータを処理するのに適した言語で、書き方にもよりますが、可読性もなかなか良いです。また使える開発者も多く、今後のメンテを誰かにお任せするのも楽そうです。</p> <p>というわけで、AWS SDK を用いて作り直したリソース一覧を取得するコードを書いてみているので、その一部を簡単にご紹介します。</p> <h4 id="まずは公式ドキュメント">まずは公式ドキュメント</h4> <p>AWS リソースって数多くの種類があり、AWS CLI コマンドのオプションも数多くありましたよね。なので SDK の API もやはり数多くあります。公式ドキュメント <a href="https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/" target="_blank">AWS SDK for JavaScript のドキュメント</a> を頼っていきましょう。</p> <p>バージョン 2 もまだ多く使われているようですが、私は新しもの好きなので AWS SDK for JavaScript、バージョン 3 のほうを使います。残念ながら英語ですがAPI リファレンスを主に参照しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.aws.amazon.com%2Fja_jp%2FAWSJavaScriptSDK%2Fv3%2Flatest%2Findex.html" title="AWS SDK for JavaScript v3" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://docs.aws.amazon.com/ja_jp/AWSJavaScriptSDK/v3/latest/index.html">docs.aws.amazon.com</a></cite></p> <p> </p> <p>今回は <a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/ec2/" target="_blank">EC2 Client</a> を使って、リソース情報を得てみましょう。NodeJS なので、まずは最初にあるモジュールの導入からですね。</p> <h4 id="EC2-Client-を初期化する">EC2 Client を初期化する</h4> <p>API リファレンスにはサンプルコードも豊富にあります。まずはサービス利用の起点となる EC2 Client を初期化してみましょう。</p> <div style="color: #cccccc; background-color: #1f1f1f; font-family: Consolas, 'Courier New', monospace; font-weight: normal; font-size: 14px; padding: 0.5em; width: 80%;"> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #c586c0;">import</span><span style="color: #cccccc;"> { </span><span style="color: #9cdcfe;">EC2Client</span><span style="color: #cccccc;"> } </span><span style="color: #c586c0;">from</span><span style="color: #cccccc;"> </span><span style="color: #ce9178;">"@aws-sdk/client-ec2"</span><span style="color: #cccccc;">;</span></div> <br /> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #569cd6;">const</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">region</span><span style="color: #cccccc;"> </span><span style="color: #d4d4d4;">=</span><span style="color: #cccccc;"> </span><span style="color: #ce9178;">'ap-northeast-1'</span><span style="color: #cccccc;">;</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #569cd6;">const</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">client</span><span style="color: #cccccc;"> </span><span style="color: #d4d4d4;">=</span><span style="color: #cccccc;"> </span><span style="color: #569cd6;">new</span><span style="color: #cccccc;"> </span><span style="color: #dcdcaa;">EC2Client</span><span style="color: #cccccc;">({ </span><span style="color: #9cdcfe;">region</span><span style="color: #cccccc;"> });</span></div> </div> <p>なおこのコードを実行する環境は、既に AWS CLI が動作する、つまり AWS 認証情報がセットされていることを前提としています。</p> <h4 id="STS-Client-を使う">STS Client を使う</h4> <p>これだけだと簡単なのですが、実は私の環境はスイッチロールが必要で、しかも MFA 認証までセットされています。この簡単なコードでは動きません。</p> <p>仕方ないので <a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/sts/" target="_blank">STS Client</a> を使って、一時的な認証情報 (アクセスキーIDやシークレットアクセスキー)を得て、それを使ってサービス初期化をおこないます。</p> <div style="color: #cccccc; background-color: #1f1f1f; font-family: Consolas, 'Courier New', monospace; font-weight: normal; font-size: 12px; padding: 0.5em; width: 80%;"> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #c586c0;">import</span><span style="color: #cccccc;"> { </span><span style="color: #9cdcfe;">STSClient</span><span style="color: #cccccc;">, </span><span style="color: #9cdcfe;">AssumeRoleCommand</span><span style="color: #cccccc;">, </span><span style="color: #9cdcfe;">AssumeRoleCommandInput</span><span style="color: #cccccc;"> } </span><span style="color: #c586c0;">from</span><span style="color: #cccccc;"> </span><span style="color: #ce9178;">"@aws-sdk/client-sts"</span><span style="color: #cccccc;">;</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #569cd6;">const</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">stsClient</span><span style="color: #cccccc;"> </span><span style="color: #d4d4d4;">=</span><span style="color: #cccccc;"> </span><span style="color: #569cd6;">new</span><span style="color: #cccccc;"> </span><span style="color: #dcdcaa;">STSClient</span><span style="color: #cccccc;">({ <span style="color: #9cdcfe;">region</span></span><span style="color: #cccccc;"> });</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #569cd6;">const</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">params</span><span style="color: #d4d4d4;">:</span><span style="color: #cccccc;"> </span><span style="color: #4ec9b0;">AssumeRoleCommandInput</span><span style="color: #cccccc;"> </span><span style="color: #d4d4d4;">=</span><span style="color: #cccccc;"> {</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">    </span><span style="color: #9cdcfe;">RoleArn</span><span style="color: #9cdcfe;">:</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">roleArn</span><span style="color: #cccccc;">, // スイッチ先ロールのARN</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">    </span><span style="color: #9cdcfe;">SerialNumber</span><span style="color: #9cdcfe;">:</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">mfaSerial</span><span style="color: #cccccc;">, // 利用するMFAのARN</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">    </span><span style="color: #9cdcfe;">TokenCode</span><span style="color: #9cdcfe;">:</span><span style="color: #cccccc;"> </span><span style="color: #cccccc;">&lt;</span><span style="color: #4ec9b0;">string</span><span style="color: #cccccc;">&gt;</span><span style="color: #4fc1ff;">mfaToken</span><span style="color: #cccccc;">, // MFA の入力キー</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">    </span><span style="color: #9cdcfe;">RoleSessionName</span><span style="color: #9cdcfe;">:</span><span style="color: #cccccc;"> </span><span style="color: #569cd6;">new</span><span style="color: #cccccc;"> </span><span style="color: #4ec9b0;">Date</span><span style="color: #cccccc;">().</span><span style="color: #dcdcaa;">getTime</span><span style="color: #cccccc;">().</span><span style="color: #dcdcaa;">toString</span><span style="color: #cccccc;">(),</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">    </span><span style="color: #9cdcfe;">DurationSeconds</span><span style="color: #9cdcfe;">:</span><span style="color: #cccccc;"> </span><span style="color: #b5cea8;">900</span><span style="color: #cccccc;">,</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">};</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #569cd6;">const</span><span style="color: #cccccc;"> { </span><span style="color: #4fc1ff;">Credentials</span><span style="color: #cccccc;"> } </span><span style="color: #d4d4d4;">=</span><span style="color: #cccccc;"> </span><span style="color: #c586c0;">await</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">stsClient</span><span style="color: #cccccc;">.</span><span style="color: #dcdcaa;">send</span><span style="color: #cccccc;">(</span><span style="color: #569cd6;">new</span><span style="color: #cccccc;"> </span><span style="color: #dcdcaa;">AssumeRoleCommand</span><span style="color: #cccccc;">(</span><span style="color: #4fc1ff;">params</span><span style="color: #cccccc;">));</span></div> </div> <p>こんな感じで得られた Credentials を EC2 Client 初期化で利用します。</p> <div style="color: #cccccc; background-color: #1f1f1f; font-family: Consolas, 'Courier New', monospace; font-weight: normal; font-size: 12px; padding: 0.5em; width: 80%;"> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #569cd6;">const</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">client</span><span style="color: #cccccc;"> </span><span style="color: #d4d4d4;">=</span><span style="color: #cccccc;"> </span><span style="color: #569cd6;">new</span><span style="color: #cccccc;"> </span><span style="color: #dcdcaa;">EC2Client</span><span style="color: #cccccc;">({</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">    </span><span style="color: #9cdcfe;">region</span><span style="color: #cccccc;">,</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">    </span><span style="color: #9cdcfe;">credentials</span><span style="color: #9cdcfe;">:</span><span style="color: #cccccc;"> {</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">        </span><span style="color: #9cdcfe;">accessKeyId</span><span style="color: #9cdcfe;">:</span><span style="color: #cccccc;"> </span><span style="color: #cccccc;">&lt;</span><span style="color: #4ec9b0;">string</span><span style="color: #cccccc;">&gt;</span><span style="color: #4fc1ff;">Credentials</span><span style="color: #cccccc;">?.</span><span style="color: #9cdcfe;">AccessKeyId</span><span style="color: #cccccc;">,</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">        </span><span style="color: #9cdcfe;">secretAccessKey</span><span style="color: #9cdcfe;">:</span><span style="color: #cccccc;"> </span><span style="color: #cccccc;">&lt;</span><span style="color: #4ec9b0;">string</span><span style="color: #cccccc;">&gt;</span><span style="color: #4fc1ff;">Credentials</span><span style="color: #cccccc;">?.</span><span style="color: #9cdcfe;">SecretAccessKey</span><span style="color: #cccccc;">,</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">        </span><span style="color: #9cdcfe;">sessionToken</span><span style="color: #9cdcfe;">:</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">Credentials</span><span style="color: #cccccc;">?.</span><span style="color: #9cdcfe;">SessionToken</span><span style="color: #cccccc;">,</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">    }</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">});</span></div> </div> <p>ちょっと面倒に感じますが、STS を利用するのは起動時の一度だけで、後は得られた認証情報を使いまわします。</p> <p>MFA の入力キー mfaToken は毎回異なるので、プログラム起動時に引数として受け取るようにしています。</p> <h4 id="EC2-インスタンス情報を得る">EC2 インスタンス情報を得る</h4> <p>さて EC2 リソース情報を得ていきましょう。各操作はコマンドオブジェクトになっていて (Commandパターンですね!)、必要なコマンドを new して EC2 Client 経由で送信します。非同期なのでサンプルでは await して結果を待っています。</p> <div style="color: #cccccc; background-color: #1f1f1f; font-family: Consolas, 'Courier New', monospace; font-weight: normal; font-size: 12px; padding: 0.5em; width: 80%;"> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #c586c0;">import</span><span style="color: #cccccc;"> { </span><span style="color: #9cdcfe;">DescribeInstancesCommand</span><span style="color: #cccccc;"> } </span><span style="color: #c586c0;">from</span><span style="color: #cccccc;"> </span><span style="color: #ce9178;">"@aws-sdk/client-ec2"</span><span style="color: #cccccc;">;</span></div> <br /> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #569cd6;">const</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">response</span><span style="color: #cccccc;"> </span><span style="color: #d4d4d4;">=</span><span style="color: #cccccc;"> <span style="color: #c586c0;">await</span> </span><span style="color: #4fc1ff;">client</span><span style="color: #cccccc;">.</span><span style="color: #dcdcaa;">send</span><span style="color: #cccccc;">(</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">   </span><span style="color: #569cd6;">new</span><span style="color: #cccccc;"> </span><span style="color: #dcdcaa;">DescribeInstancesCommand</span><span style="color: #cccccc;">({})</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">)</span><span style="color: #cccccc;">;</span></div> </div> <p>なお、今回の説明では、全インスタンスを得るために特に属性は指定しません。</p> <p>さて、得られた response には Reservations 属性があり、そこに Instances の配列があります。この Instances が各 EC2 インスタンスの情報の配列になっています。</p> <p>例えば以下のように EC2 インスタンス情報を参照します。プログラムなので二重の配列なんかの処理も簡単に対応できます。</p> <div style="color: #cccccc; background-color: #1f1f1f; font-family: Consolas, 'Courier New', monospace; font-weight: normal; font-size: 12px; padding: 0.5em; width: 80%;"> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #c586c0;">if</span><span style="color: #cccccc;"> (</span><span style="color: #4fc1ff;">response</span><span style="color: #cccccc;"> </span><span style="color: #d4d4d4;">&amp;&amp;</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">response</span><span style="color: #cccccc;">.</span><span style="color: #9cdcfe;">Reservations</span><span style="color: #cccccc;">) {</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">   </span><span style="color: #c586c0;">for</span><span style="color: #cccccc;"> (</span><span style="color: #569cd6;">const</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">reservations</span><span style="color: #cccccc;"> </span><span style="color: #569cd6;">of</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">response</span><span style="color: #cccccc;">.</span><span style="color: #9cdcfe;">Reservations</span><span style="color: #cccccc;">) {</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">       </span><span style="color: #c586c0;">if</span><span style="color: #cccccc;"> (</span><span style="color: #4fc1ff;">reservations</span><span style="color: #cccccc;">.</span><span style="color: #9cdcfe;">Instances</span><span style="color: #cccccc;">) {</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">           </span><span style="color: #c586c0;">for</span><span style="color: #cccccc;"> (</span><span style="color: #569cd6;">const</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">instance</span><span style="color: #cccccc;"> </span><span style="color: #569cd6;">of</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">reservations</span><span style="color: #cccccc;">.</span><span style="color: #9cdcfe;">Instances</span><span style="color: #cccccc;">) {</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">                   </span><span style="color: #6a9955;">// ここに各インスタンスの出力処理</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">           }       </span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">       }</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">   }</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">}</span></div> </div> <h4 id="CSV-化して出力する">CSV 化して出力する</h4> <p>実際に利用する部分ですが、CSV 化して出力する部分は、どうとでも書けるとおもいます。参考にはあまり適していませんが、配列にいれたデータを標準出力に出す例をいちおう紹介しますね。</p> <p>以下みたいなベタに配列を作成する感じ。各属性の種類に応じて、文字列化の処理を工夫したりも簡単です。もっと多くの属性値が取れますが、以下のサンプルでは一部だけにしています。</p> <div style="color: #cccccc; background-color: #1f1f1f; font-family: Consolas, 'Courier New', monospace; font-weight: normal; font-size: 12px; padding: 0.5em; width: 80%;"> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #569cd6;">const</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">header</span><span style="color: #d4d4d4;">:</span><span style="color: #cccccc;"> </span><span style="color: #4ec9b0;">string</span><span style="color: #cccccc;">[] </span><span style="color: #d4d4d4;">=</span><span style="color: #cccccc;"> [</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">    </span><span style="color: #ce9178;">'ImageId'</span><span style="color: #cccccc;">,</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">    </span><span style="color: #ce9178;">'InstanceType'</span><span style="color: #cccccc;">,</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">];</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #569cd6;">const</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">data</span><span style="color: #cccccc;"> </span><span style="color: #d4d4d4;">=</span><span style="color: #cccccc;"> [</span><span style="color: #4fc1ff;">header</span><span style="color: #cccccc;">];</span></div> <br /> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #c586c0;">if</span><span style="color: #cccccc;"> (</span><span style="color: #4fc1ff;">response</span><span style="color: #cccccc;"> </span><span style="color: #d4d4d4;">&amp;&amp;</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">response</span><span style="color: #cccccc;">.</span><span style="color: #9cdcfe;">Reservations</span><span style="color: #cccccc;">) {</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">    </span><span style="color: #c586c0;">for</span><span style="color: #cccccc;"> (</span><span style="color: #569cd6;">const</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">reservations</span><span style="color: #cccccc;"> </span><span style="color: #569cd6;">of</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">response</span><span style="color: #cccccc;">.</span><span style="color: #9cdcfe;">Reservations</span><span style="color: #cccccc;">) {</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">        </span><span style="color: #c586c0;">if</span><span style="color: #cccccc;"> (</span><span style="color: #4fc1ff;">reservations</span><span style="color: #cccccc;">.</span><span style="color: #9cdcfe;">Instances</span><span style="color: #cccccc;">) {</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">            </span><span style="color: #c586c0;">for</span><span style="color: #cccccc;"> (</span><span style="color: #569cd6;">const</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">instance</span><span style="color: #cccccc;"> </span><span style="color: #569cd6;">of</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">reservations</span><span style="color: #cccccc;">.</span><span style="color: #9cdcfe;">Instances</span><span style="color: #cccccc;">) {</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">                </span><span style="color: #4fc1ff;">data</span><span style="color: #cccccc;">.</span><span style="color: #dcdcaa;">push</span><span style="color: #cccccc;">([</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">                    </span><span style="color: #4fc1ff;">instance</span><span style="color: #cccccc;">.</span><span style="color: #9cdcfe;">ImageId</span><span style="color: #cccccc;">,</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">                    </span><span style="color: #4fc1ff;">instance</span><span style="color: #cccccc;">.</span><span style="color: #9cdcfe;">InstanceType</span><span style="color: #cccccc;">,</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">                ]);</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">            }</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">        }</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">    }</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">}</span></div> <br /> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #c586c0;">for</span><span style="color: #cccccc;"> (</span><span style="color: #569cd6;">const</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">line</span><span style="color: #cccccc;"> </span><span style="color: #569cd6;">of</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">data</span><span style="color: #cccccc;">) {</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">    </span><span style="color: #569cd6;">const</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">cols</span><span style="color: #cccccc;"> </span><span style="color: #d4d4d4;">=</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">line</span><span style="color: #cccccc;">.</span><span style="color: #dcdcaa;">map</span><span style="color: #cccccc;">(</span><span style="color: #9cdcfe;">col</span><span style="color: #cccccc;"> </span><span style="color: #569cd6;">=&gt;</span><span style="color: #cccccc;"> (</span><span style="color: #9cdcfe;">col</span><span style="color: #cccccc;"> </span><span style="color: #d4d4d4;">||</span><span style="color: #cccccc;"> </span><span style="color: #ce9178;">''</span><span style="color: #cccccc;">).</span><span style="color: #dcdcaa;">toString</span><span style="color: #cccccc;">().</span><span style="color: #dcdcaa;">replace</span><span style="color: #cccccc;">(</span><span style="color: #d16969;">/"/</span><span style="color: #569cd6;">g</span><span style="color: #cccccc;">, </span><span style="color: #ce9178;">'""'</span><span style="color: #cccccc;">));</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">    </span><span style="color: #9cdcfe;">console</span><span style="color: #cccccc;">.</span><span style="color: #dcdcaa;">log</span><span style="color: #cccccc;">(</span><span style="color: #ce9178;">'"'</span><span style="color: #cccccc;"> </span><span style="color: #d4d4d4;">+</span><span style="color: #cccccc;"> </span><span style="color: #4fc1ff;">cols</span><span style="color: #cccccc;">.</span><span style="color: #dcdcaa;">join</span><span style="color: #cccccc;">(</span><span style="color: #ce9178;">'","'</span><span style="color: #cccccc;">) </span><span style="color: #d4d4d4;">+</span><span style="color: #cccccc;"> </span><span style="color: #ce9178;">'"'</span><span style="color: #cccccc;">);</span></div> <div style="line-height: 1.2; font-size: 12px; white-space: pre; margin-bottom: 0;"><span style="color: #cccccc;">}</span></div> </div> <h4 id="この先は">この先は</h4> <p>コマンドが多くて最初は戸惑うのですが、AWS CLI で指定するコマンドとほぼ類似であり、コマンド名も似ているので、AWS CLI を使った経験があればすぐに馴染めるとおもいます。</p> <p>API リファレンスを見ながらいろいろ試し、結果として得られた JSON 化された情報をいろいろ操作して、学んでいきましょう。</p> <p>私の作成しているツールも対象となる AWS リソースの拡大をしつつ、コマンド送信時のリトライ処理を追加したり、CSV に出力しないで NoSQL DB である <a href="https://www.npmjs.com/package/nedb" target="_blank">nedb</a> に一旦インサートしたり、など拡張を続けています。</p> <h4 id="最後に">最後に</h4> <p>というわけで、今回はコード多めな感じになりました。</p> <p>私も書き始めで、まだ無駄がありそうなコードですが、まあ、こんな感じで使えるんだな、と生暖かい目で見ていただければとおもいます。</p> <p>読んでいただきありがとうございました。マネックスの採用にご興味のある方は、ぜひ以下募集をご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.monexgroup.jp%2Frecruit%2F" title="マネックス証券株式会社 採用サイト" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.monexgroup.jp/recruit/">www.monexgroup.jp</a></cite></p> <p> </p> <p> </p> <p> </p> toshio_yamashita 改めて、第二の内製化へ hatenablog://entry/6801883189072195811 2024-01-04T13:03:42+09:00 2024-01-04T13:37:44+09:00 2024年が始まりましたね。 新年一発目の担当は、システム開発三部長 兼 マネックス・ラボ長の上川です。 マネックスとしては、この1月からNTTドコモ様との資本提携がいよいよ本格化する年とはなりますが、今年は新年早々大きな天災や事故が起きる痛ましい年となってしまっており、被害にあわれた方々には、心よりお見舞い申し上げます。 これ以上は何も悲劇が起こらないことを祈りたいです。 次世代システムって、、 さて、不定期に思い出したように記事を投稿している次世代システムの話ですが、一体今はどうなっているのでしょうか。 スタートはこのあたりの記事ですね。 blog.tech-monex.com 途中で、「… <p>2024年が始まりましたね。</p> <p>新年一発目の担当は、システム開発三部長 兼 マネックス・ラボ長の上川です。</p> <p>マネックスとしては、この1月からNTTドコモ様との資本提携がいよいよ本格化する年とはなりますが、今年は新年早々大きな天災や事故が起きる痛ましい年となってしまっており、被害にあわれた方々には、心よりお見舞い申し上げます。</p> <p>これ以上は何も悲劇が起こらないことを祈りたいです。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kingblogger/20240104/20240104113330.jpg" width="1200" height="896" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <h3 id="次世代システムって">次世代システムって、、</h3> <p>さて、不定期に思い出したように記事を投稿している次世代システムの話ですが、一体今はどうなっているのでしょうか。</p> <p>スタートはこのあたりの記事ですね。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2021%2F04%2F09%2F171852" title="次世代システム、始めます - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2021/04/09/171852">blog.tech-monex.com</a></cite></p> <p>途中で、「まだ頑張って継続中です」という趣旨の報告記事も書きましたが、現状はいよいよ大詰めを迎える段階に来ており、無事一区切りをつけられるように開発メンバー一同鋭意クロージングに向けて頑張っております。</p> <h4 id="新しい試みを後押ししてくれるのがマネックスの強さ">新しい試みを後押ししてくれるのが、マネックスの強さ</h4> <p>当初考えていたスケジュール通りには進まず、いろいろ計画を変更したり体制を変更しながら進んできたプロジェクトですが、「開発体制」「開発プラットフォーム」「採用する技術」「プロジェクトの進め方」など、何もかもが新しい試みで進めてきたことで、社内のナレッジの幅や対応力に広がりを持たせることができましたし、何より</p> <p><strong>「新しいことをやろうとしたときに、何が問題になりやすいのか、自分たちには何が足りないのか」</strong></p> <p>ということを、一般論ではなく今のマネックスのシステム部門の体制の中で起こること、として実際に経験して理解を深めることができたのが、大変貴重な財産です。</p> <p>ここで得られた経験を活かして始まっている他の大型プロジェクトもすでにあります。</p> <p>100%研究開発しか意義がない、ということではなく、ビジネスやサービス面とのバランスを上手にとりながらではありますが、どちらかと言えば「システム部門としての経験値を上げる」ことを主眼にしたプロジェクトを、重要なことだと理解して広い心で後押ししてくれるのが、マネックスの強さであり、変革していく力の源なのだと思います。</p> <p>この期待に、しっかり応えていけるようにしたいですね。</p> <h3 id="第二の内製化">第二の内製化</h3> <p>以前マネックスは「第二の創業」というキーワードを掲げて、変革をリードしていく存在になることを宣言しましたが、2017年に基幹システムを内製化したマネックスも、次世代システムに向けて、「第二の内製化」を進めます。</p> <p>その土台になるプロジェクトが、上にも書いた通りいよいよ大詰め。</p> <p>「ホップ、ステップ、ジャンプ」で言うなら、2017年の内製化がホップ、今回のプロジェクトがステップです。</p> <p>ステップが無事にできたら、いよいよジャンプですね。</p> <p>0から1を生み出すのには少々時間がかかってしまいましたが、その試行錯誤を活かし、1から10そして100へ向かう道のりはどんどんスピードアップしていけるようにしたいです。</p> <p>ジャンプの大枠の構想はもう出来上がっていますし、少しずつ始めてもいます。</p> <p>最初のリンク記事の中にもありますが、証券基幹システムのカギはやはり、「余力」ですので(「余力」とは?という方は上のリンク先の記事の下の方で簡単に紹介しています)ここから始まるわけですが、詳細を紹介するのは次の機会にしたいと思います。</p> <p>プロジェクトの区切りではない微妙なタイミングの記事でしたので、新年の挨拶のような抽象的な話で終わってしまいますが、今年のマネックスは会社として「非連続的な成長」を目指して変わっていく年であり、システム部門としてもそれを支えていけるようにどんどん強くなっていきます。</p> <p>そんな、成長の機会がたくさんあるマネックスのシステム部門に興味があるなと思った方は、ぜひ下のリンクから気軽にご連絡をください。</p> <p>一緒に変わっていきましょう!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fmonex%2Fhomes%2F2366" title="募集一覧 / マネックスグループ株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/monex/homes/2366">open.talentio.com</a></cite></p> <div class="entry-writer"> <div class="image-round"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kingblogger/20200422/20200422215827.jpg" /></div> <span class="name">上川 和樹</span><span class="job">システム開発三部長 兼 システム開発三部 マネックス・ラボ長</span></div> kingblogger リモート&出社、ハイブリッドの未来 hatenablog://entry/6801883189068302067 2023-12-22T10:00:00+09:00 2023-12-22T10:00:03+09:00 こんにちは、新卒3年目の野地です。 最近、沖縄で100kmウルトラマラソンに初挑戦、無事完走してきました。 一人前のエンジニアに必要な精神と体力を、走ることで養っています(笑)。 さて、今回はリモートワークについて、です。 リモート vs 出社 新型コロナが5類になって久しく、米大手テック企業等がフルリモートを廃止したりしているようです。 自分の所属グループ(25人くらい)でも度々どちらの方がいいか・どういう風にするのがいいか、ということが話題に上がります。 そんな中で最近、GitLabの「オールリモートガイド」というものを目にしました。※オールリモートはフルリモートみたいなもの リモートワー… <p><span style="color: #4c4c4c; font-family: 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', メイリオ, Meiryo, Osaka, 'MS Pゴシック', 'MS PGothic', sans-serif; font-size: 15.3333px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.46px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: #ffffff; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">こんにちは、</span><span style="color: #4c4c4c; font-family: 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', メイリオ, Meiryo, Osaka, 'MS Pゴシック', 'MS PGothic', sans-serif; font-size: 15.3333px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.46px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: #ffffff; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">新卒3年目の野地です。</span></p> <p><span style="color: #4c4c4c; font-family: 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', メイリオ, Meiryo, Osaka, 'MS Pゴシック', 'MS PGothic', sans-serif; font-size: 15.3333px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.46px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: #ffffff; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">最近、沖縄で100kmウルトラマラソンに初挑戦、無事完走してきました。</span></p> <p>一人前のエンジニアに必要な精神と体力を、走ることで養っています(笑)。</p> <p> </p> <p>さて、今回はリモートワークについて、です。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/daiki_noji/20231220/20231220130038.png" width="1200" height="686" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <h3 id="リモート-vs-出社">リモート vs 出社</h3> <p>新型コロナが5類になって久しく、米大手テック企業等がフルリモートを廃止したりしているようです。</p> <p>自分の所属グループ(25人くらい)でも度々どちらの方がいいか・どういう風にするのがいいか、ということが話題に上がります。</p> <p>そんな中で最近、GitLabの「<a href="https://handbook.gitlab.com/handbook/company/culture/all-remote/guide/" target="_blank">オールリモートガイド</a>」というものを目にしました。<br />※オールリモートはフルリモートみたいなもの</p> <p>リモートワークで高い生産性(GitLabが実際にそうなのかは分かりませんが....)を維持するための具体的な方法の例を知った一方、フルリモートだからこそルールの徹底や「オールリモート」の価値観を共有しやすいのだろうとも思いました。</p> <p>当社では、リモートワークなど存在しなかったときから働いている社員もいれば、コロナまっただ中でリモートワークネイティブな社員もいます(自分がこれです)。<br />また、孤独感を感じやすい・監視が無いとサボり続ける等、気質として向き不向きや、家庭の事情でリモートワークせざるを得ないといった制約もあります。<br />当社の業態上、個人情報の扱いなどセキュリティの観点からフルリモートにはなりえませんし、「全員基本出社で」とするにはもうオフィスの座席が足りません(おかげさまで社員がどんどん増えています)。</p> <p>よって、リモート/出社ハイブリッドで、当社にとって良いやり方を模索していく必要があります。</p> <h3 id="口頭とテキスト">口頭とテキスト</h3> <p>GitLabの「オールリモートガイド」に以下があります。</p> <blockquote> <p>3. Writing down and recording knowledge (over verbal explanations).</p> <p>(口頭での説明よりも)知識を書き留めて記録する。</p> </blockquote> <p>出社している人同士の対面・口頭でのコミュニケーションは手軽かつ表情やボディランゲージも使えるので当事者間での情報伝達には効率が良いです。</p> <p>一方で、会話に参加していないリモートワーカーに情報が伝わらなかったり、会話の内容を忘れてしまったり、ということがあります。<br />皆さんも会話した内容をグループチャットに流したことがあるのではないでしょうか?</p> <p>ただ、全ての会話・全ての会議について逐一そのようなことを自分でやるのは出社している人にとっては面倒くさすぎるでしょう。</p> <h3 id="テクノロジーでの解決">テクノロジーでの解決</h3> <p>WEB会議であれば、全ての会議を文字起こしして、要約して共有することができます。</p> <p>対面・口頭の会話であれば、例えばiPhoneのChatGPTアプリに聞きとらせて要約して共有できます。</p> <p>技術的には既に解決できるのですが、例えば当社であれば、経営に関わるような重要な情報を生成AIサービスの入力に使うことは禁じられているので、そのルールを変えたり、入力してはいけない情報が含まれる会議か会話かをどういう運用にすれば判別できるか、とかそういうことを考えていかなければ、実際に会社内で活用していくには至りません。</p> <p>まあ、これはブロックチェーンとか、新しい技術を社会実装していく際には共通していることですね。</p> <p> </p> <p>さて、今年を振り返ってみて、みなさんの職場はどうでしたでしょうか?<br />会社の風通しはよいですか?</p> <p>当社開発本部では社員の意見により働き方の改善に努めており、まだ発展途上です。<br />働く環境を自分の手で良くしていってやるぜ!という方は、下のボタンから募集をご覧ください!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fmonex%2Fhomes%2F2366" title="募集一覧 / マネックスグループ株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/monex/homes/2366">open.talentio.com</a></cite></p> daiki_noji 2023年の振返り hatenablog://entry/6801883189064000529 2023-12-08T09:32:38+09:00 2023-12-08T09:32:38+09:00 こんにちは。開発本部長の後藤です。 2023年も残り3週間とちょっととなってきました。本当にありがとうございました。今回は今年を振り返りです。 開発の振返り 2023年は、いろいろありましたが、特に来年1月のイオン銀行様との提携開始に向けて開発本部は、連携アプリの開発やデータ移行の準備をしてきました。特に来年から始まる新NISAとのタイミングが重なっており、より慎重な開発を進めてきました。 おかげさまで、提携開始に向けて準備はできており、あと、年末年始の作業を行う状況となっております。 さらに、先月(11月)にNTTドコモ様との資本提携を発表しました。 現在、NTTドコモ様とどのような連携サー… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/G/Gotokun/20231204/20231204150246.jpg" width="640" height="427" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><BR> こんにちは。開発本部長の後藤です。<BR> 2023年も残り3週間とちょっととなってきました。本当にありがとうございました。今回は今年を振り返りです。<BR></p> <h3 id="開発の振返り">開発の振返り</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/G/Gotokun/20231204/20231204151108.jpg" width="1200" height="795" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <BR><BR>2023年は、いろいろありましたが、特に来年1月のイオン銀行様との提携開始に向けて開発本部は、連携アプリの開発やデータ移行の準備をしてきました。特に来年から始まる新NISAとのタイミングが重なっており、より慎重な開発を進めてきました。<BR> おかげさまで、提携開始に向けて準備はできており、あと、年末年始の作業を行う状況となっております。<BR><BR> さらに、先月(11月)にNTTドコモ様との資本提携を発表しました。<BR> 現在、NTTドコモ様とどのような連携サービスをすれば、お客様の利便性があがるかなど、絶賛検討しているところです。<BR> いくつかは、具体的になってきていますが、ここではお伝え出来ないことが残念、乞うご期待といったところでしょうか。<BR> 時間がかかるものもありますが、いままでマネックスが作ってきた様々なサービスをNTTドコモ様とコラボしていきたいと考えています。<BR><BR> これらと並行して、マネックスのシステムは内製化をしていますが、個別サービスのリプレイスもすすめたり、将来を目指した新構成の検討などは来年からは本格化する予定です。<BR></p> <h3 id="インフレーション">インフレーション</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/G/Gotokun/20231204/20231204150726.jpg" width="1200" height="847" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <BR>お札が、羽根をつけて飛んでいっちゃってますね~~~ &lt;(_ _)> これは一万円札なのだろうか<BR> 海外ではインフレになってきており、日本も少しづつインフレになってきていると実感しています。<BR> インフレとは、ご存じのとおりお金の価値が下がるということ、さらに円安になっているので海外から見ると更に日本円の価値が下がってしまっているということです。<BR> 金利動向によりますが、シンプルにいうと銀行預金などをやっていると・・・・・・・・ですね。 タンス預金などもってのほかですね。<BR> 来年から、新NISAも始まるので預金から有価証券という流れが加速するのじゃないかなと思ってます。<BR> 余談で、個人的なところですが、30代の頃にローンを組んで家を買いました。今返済は終わっていますが、返済中に嵐のようなインフレが起きないものかと、常々考えていたことがありましたが、結局この20数年デフレだったんで、なんだかな~~と、、、<BR></p> <h3 id="まとめ">まとめ</h3> <p>さて、今年を振り返ってみて、みなさんの職場はどうでしたでしょうか?会社は風通しはよいですか?<BR> 当社開発本部では、社員の意見を聞き開発、保守をすすめています。やっとコロナが終わって社員とも顔をあわせる機会が多くなってきています。<BR> NTTドコモさんとの連携は、ここ数年は開発をしていきますし、他にもビックプロジェクトは盛りだくさんですので、是非一緒にやっていきたいという方は、下の<B><a href="https://open.talentio.com/r/1/c/monex/homes/2366">キャリア採用はこちら</a></B>ボタンをクリックしてください。<BR><BR> では、みなさま良いお年を迎えてください。 ありがとうございました。<BR></p> Gotokun ブランドロゴ hatenablog://entry/6801883189049615428 2023-11-10T17:32:59+09:00 2023-11-10T17:32:59+09:00 こんにちは。今回はブランドロゴについてまとめてみました。 ブランドロゴはブランドイメージを定着させるため、また創業時の<想い>なども込められた重要なものになります。マネックスグループのブランドシンボルについてもこちらにしっかり説明してあります。 www.monexgroup.jp 折に触れて社名とデザインの意味、人型(ジャイアントステップといいます)であることを聞くことも多いです。また、ブランドイメージを損なわないよう、使用についてガイドラインで基準が規定されています。 ところで昨今、ブランドロゴを一新する企業は多いとか。少し前にはX(旧Twitter)も話題になりましたね。 実は当社(マネッ… <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/rereoji/20231110/20231110172935.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>こんにちは。今回はブランドロゴについてまとめてみました。</p> <p>ブランドロゴはブランドイメージを定着させるため、また創業時の&lt;想い&gt;なども込められた重要なものになります。<br />マネックスグループのブランドシンボルについてもこちらにしっかり説明してあります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.monexgroup.jp%2Fjp%2Fcompany%2Flogo.html" title="ブランドシンボル | 会社情報 | マネックスグループ株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.monexgroup.jp/jp/company/logo.html">www.monexgroup.jp</a></cite></p> <p><br /><br /></p> <p>折に触れて社名とデザインの意味、人型(ジャイアントステップといいます)であることを聞くことも多いです。また、ブランドイメージを損なわないよう、使用についてガイドラインで基準が規定されています。</p> <p>ところで昨今、ブランドロゴを一新する企業は多いとか。<br />少し前にはX(旧Twitter)も話題になりましたね。</p> <p>実は当社(マネックス証券)とマネックスグループのロゴ、進化してました!<br />今年6月から変わっているのですがお気づきになりましたか。</p> <p>ジャイアントステップは踏襲してロゴを変更しています。</p> <p> </p> <h3 id="ロゴの変更について担当者に質問してみました">ロゴの変更について担当者に質問してみました</h3> <p>ロゴはマネックス証券のデザインセンターで管理されています。今回の変更についてデザインセンターのデザインディレクターに質問してみました。</p> <h4 id="なぜロゴ変更することになったのでしょうか">なぜロゴ変更することになったのでしょうか。</h4> <p><br />お客様のご利用環境を鑑みて、昨今、特にスマートフォンやスマートウォッチなどの小さなディスプレイでの情報取得機会が増えており、そのような環境でのロゴディスプレイの最適化のため変更を行いました。</p> <h4 id="変更のポイントを教えてください">変更のポイントを教えてください。</h4> <p><br />もともとシンボルとロゴタイプの組み合わせについても革新と伝統の組み合わせというコンセプトがあり、今回のロゴタイプの変更においてもコンセプトは変えずに書体のみ変更を行っています。</p> <h4 id="ロゴ変更の反響はいかがでしょうか">ロゴ変更の反響はいかがでしょうか。</h4> <p><br />まだ主だった反響などはありませんが、社内からは早速よい反応をいただいています。</p> <h4 id="アップルスタバマックのようにシンボルマークだけにはしないのでしょうか">アップル、スタバ、マックのようにシンボルマークだけにはしないのでしょうか。</h4> <p><br />グループ内にMONEXブランドのDNAを引き継いで事業を展開する会社も複数あり、それらの会社のロゴマークにもシンボルマークである「ジャイアントステップ」がロゴマークに使用されています。そのような背景からも現状シンボルマークだけの使用は検討しておりませんが、一部、省力可能な状況が十分に判断できるシーンではシンボルマークを単体で使用している事例もあります。</p> <p> </p> <h3 id="ロゴの変更のきっかけ">ロゴの変更のきっかけ</h3> <p>昨年のマネックスグループの株主総会でロゴの字体が古いというご意見をいただき、1年後には新ロゴの変更となりました。<br />マネックスに所属しているとお客様、関係者等々のご意見を真摯に受け止めていることを常々感じます。 一人一人のご意見を大切にしているというグループ姿勢が垣間見える例ではないでしょうか。</p> rereoji 近年のモジュールバンドラについて hatenablog://entry/6801883189052994088 2023-10-27T09:48:10+09:00 2023-10-27T09:48:10+09:00 こんにちは、システム開発一部の橋本です。 前回はT3Stackについて触れてきました。 blog.tech-monex.com 上記記事に関連し、現代のWebアプリケーション開発における欠かせないツールとしてモジュールバンドラが上がると思います。今回は近年台頭しているモジュールバンドラについて簡単に調べてみたのでそちらをご紹介していきたいと思います。 モジュールバンドルについて モジュールバンドル(Module bundling)は、Web開発において、JavaScriptモジュールやCSSファイルなどのリソースを、一つのファイルにまとめてバンドル化するプロセスを指します。これは、Webアプリ… <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kahashimoto/20231024/20231024102357.png" width="322" height="322" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>こんにちは、システム開発一部の橋本です。</p> <p>前回はT3Stackについて触れてきました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2023%2F06%2F23%2F092250" title="Webアプリケーション開発への新たな風 T3 Stack - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2023/06/23/092250">blog.tech-monex.com</a></cite></p> <p>上記記事に関連し、現代のWebアプリケーション開発における欠かせないツールとしてモジュールバンドラが上がると思います。<br />今回は近年台頭しているモジュールバンドラについて簡単に調べてみたのでそちらをご紹介していきたいと思います。</p> <h4 id="モジュールバンドルについて">モジュールバンドルについて</h4> <p>モジュールバンドル(Module bundling)は、Web開発において、JavaScriptモジュールやCSSファイルなどのリソースを、一つのファイルにまとめてバンドル化するプロセスを指します。これは、Webアプリケーションのパフォーマンス向上やファイルの管理を容易にするために使用されます。</p> <h4 id="モジュールバンドルの経年変化">モジュールバンドルの経年変化</h4> <p>モジュールバンドラのデファクトスタンダードとして、<a href="https://webpack.js.org/" target="_blank">Webpack</a>は長らく広く利用されてきましたが、課題もいくつかありました。大規模な開発になるほどアプリケーションの肥大化に伴いビルド速度が遅くなったり、Webpackの設定は非常に強力で柔軟ですが初心者や新規参画者にとっては複雑に感じることがあり、設定ファイルが肥大化するリスクが顕在していたり、、<br />そういった使用に関連するいくつかの課題が浮かび上がり、新しいツールやアプローチが登場しています。</p> <h4 id="近年台頭しているモジュールバンドラ">近年台頭しているモジュールバンドラ</h4> <p>上記を踏まえ、近年登場している次世代のモジュールバンドラをご紹介します。</p> <h5 id="Vite">Vite</h5> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fja.vitejs.dev%2F" title="Vite" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://ja.vitejs.dev/">ja.vitejs.dev</a></cite></p> <p>Viteは、高速な開発サーバーとESモジュールを活用することで、JavaScriptモジュール化の課題に対処しており、従来のバンドルツールに比べて迅速な起動とホットリロードを提供し、大規模プロジェクトにおいてもパフォーマンスの向上を実現します。<br />詳しい内容は以下公式サイトに記載しています。</p> <p><a href="https://ja.vitejs.dev/guide/why.html" target="_blank">Vite を使う理由 | Vite</a></p> <h5 id="Turbopack">Turbopack</h5> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fturbo.build%2Fpack" title="Index – Turbopack" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://turbo.build/pack">turbo.build</a></cite></p> <p><br />TubopackはNext.jsをリリースしたVercelが開発したモジュールバンドラとなり、もともとはNext.jsでもWebpackを利用しています。<br />そのうえで、Webpackでの問題点をTurbopackで置き換えることで解消することが狙いで作成されました。<br />またTurbopackではRustを採用し、Turboエンジンと呼ばれるライブラリを使用して、関数レベルのキャッシングを実現しており、コードの一部だけをコンパイルすることで高速な増分更新を提供します。このアプローチにより、開発時の変更への迅速な対応と、将来の高速な本番ビルドが可能となっております。<br />詳しい内容は以下公式サイトに記載しています。</p> <p><a href="https://turbo.build/pack/docs/core-concepts">Core Concepts – Turbopack</a></p> <h5 id="Rspack">Rspack</h5> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.rspack.dev%2F" title="Rspack" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.rspack.dev/">www.rspack.dev</a></cite></p> <p><br />RspackはTurbopackと同じRustから作られたモジュールバンドラです。ViteやTurbopackとは異なり、webpackエコシステムとの連携を提供しているため、独自の特徴を備えています。そのうえで高速な開発サイクルを提供しています。<br />またByteDance社でのパフォーマンス問題を解決するために社内で作成されており、開発サーバーのスタートアップパフォーマンス、高速なビルド、柔軟な設定、本番環境の最適化能力など、さまざまな要件を満たすことを目指しているようです。</p> <h4 id="優位性について">優位性について</h4> <p>3つのモジュールバンドラを紹介しましたが、それぞれに異なる優れた点があるため、選択はプロジェクトの要件と優先順位に依存します。</p> <ul> <li>Viteは優れた開発者体験と開発時のパフォーマンスを提供し、本番ビルドにはRollupに依存しています。開発スピードとエクスペリエンスに焦点を当てており、これらが重要である場合はViteが適しているでしょう。</li> <li>TurbopackはRustで開発され、Viteと同様に開発時の変更に素早く対応しますが、現在はベータ版であるため、新規プロジェクトでの採用には注意が必要かもしれません。</li> <li>RspackもRustで開発され、Webpackエコシステムと連携して大規模なアプリケーションのビルド時間を短縮し、本番環境での最適化を提供します。既存のWebpackプロジェクトにも適用できるため、パフォーマンスの向上を求めるプロジェクトに価値があるでしょう。</li> </ul> <h4 id="まとめ">まとめ</h4> <p>モジュールバンドラの進化は、Web開発に新たな可能性を提供し、プロジェクトに合わせた最適な選択肢が増えたことで、開発効率とパフォーマンスを向上させるチャンスが広がっています。是非、プロジェクトの要件に合致するツールを選んで活用してみてください。</p> kahashimoto type転職フェアに出展しました hatenablog://entry/820878482967341180 2023-10-12T10:46:13+09:00 2023-10-12T10:47:40+09:00 こんにちは。システム開発部の原田です。2023/09/09(土)、渋谷ヒカリエにて type女性の転職フェア へ参戦してきました。 この転職フェアというのは一般的に面接などを行う場ではなく、転職を検討していたり、すぐは考えてないもののどんな企業があるのか知りたいという方へうちはこんな会社です!と会社の説明をさせて頂いたり、働く環境のPRをさせていただくイベントになります。ITエンジニアという職種は昨今大分浸透してきたものの、まだまだ弊社でもエンジニアの女性比率が少ない状況です。システム開発部としてはもっと女性の意見も取り入れた職場にしていきたいということで女性の方にもっとマネックス証券のことを… <p>こんにちは。システム開発部の原田です。<br />2023/09/09(土)、渋谷ヒカリエにて <strong>type女性の転職フェア </strong>へ参戦してきました。</p> <p><br />この転職フェアというのは一般的に面接などを行う場ではなく、<br />転職を検討していたり、すぐは考えてないもののどんな企業があるのか知りたい<br />という方へうちはこんな会社です!と会社の説明をさせて頂いたり、働く環境のPRをさせていただくイベントになります。<br /><br />ITエンジニアという職種は昨今大分浸透してきたものの、まだまだ弊社でもエンジニアの女性比率が少ない状況です。<br />システム開発部としてはもっと女性の意見も取り入れた職場にしていきたいということで<br />女性の方にもっとマネックス証券のことを知っていただきたく、今回女性の転職フェアへ出展をさせていただきました。</p> <p> </p> <h3 id="午前中は設営などの準備や会場ルールの再確認をして">午前中は設営などの準備や会場ルールの再確認をして…</h3> <div class="images-row mceNonEditable"> <div class="images-row-item"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/monex_engineer/20230914/20230914165922.jpg" width="600" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></div> <div class="images-row-item"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/monex_engineer/20230914/20230914131513.jpg" width="800" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></div> </div> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/monex_engineer/20230914/20230914170133.jpg" width="450" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /> <img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/monex_engineer/20230914/20230914170039.jpg" width="450" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p> </p> <h3 id="午後からいざ開場です"><strong>午後からいざ開場です。</strong></h3> <p>前日は台風で電車が止まったりしており懸念もあったのですが<br />当日は天気も回復し、会場内はそんな懸念を吹っ飛ばすほどの大盛況でした。</p> <p>弊社のブースも大変うれしいことにたくさんの方にご来場いただき、<br />マネックス証券での業務内容や働く環境についてたくさんの方に聞いていただくことが出来ました。</p> <p>来場された方にお声がけをすることもあったのですが、実は結構ドキドキしながらお声をかけさせていただいておりました。笑</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/monex_engineer/20230914/20230914145028.jpg" width="672" height="639" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/monex_engineer/20230914/20230914145133.jpg" width="522" height="660" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p> </p> <p>一部抜粋になりますが、下記のような資料とともに説明をさせていただきました。</p> <div class="images-row mceNonEditable"> <div class="images-row-item"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/monex_engineer/20230914/20230914171026.png" width="1200" height="824" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></div> <div class="images-row-item"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/monex_engineer/20230914/20230914171159.png" width="1200" height="810" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></div> </div> <p> </p> <h3 id="これからの出展予定">これからの出展予定</h3> <p>今までも色々な転職フェアに出展をさせて頂いており、<br />転職を考えている方もそうでない方へもマネックス証券についてお話する機会を頂き大変喜ばしく感じています。<br />様々な企業の話を聞く事で商品券が貰えたり…なんてフェアもありました。<br /><br />以後の出展予定としては下記を予定していますので、転職意向の有無にかかわらず是非お立ち寄りいただき、お話を聞いてくださると嬉しいです。</p> <ul> <li>オンラインでのtype転職フェア(10月中旬頃に特設ページで詳細開示予定)</li> <li>来場型でのtype転職フェア(11月末頃に特設ページで詳細開示予定)</li> </ul> <p> </p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.monexgroup.jp%2Frecruit%2F" title="マネックス証券株式会社 採用サイト" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.monexgroup.jp/recruit/">www.monexgroup.jp</a></cite></p> <p> </p> <p> </p> <div class="entry-writer"> <div class="image-round"><img class="hatena-fotolife" title="f:id:monex_engineer:20190517152440p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/monex_engineer/20230914/20230914120722.png" alt="f:id:monex_engineer:20190517152440p:plain" /></div> <span class="name">原田 恵梨華</span><span class="job">システム開発部 内製開発グループ エンジニア</span></div> monex_engineer What are Trading Technologies? hatenablog://entry/820878482968881795 2023-09-22T14:57:25+09:00 2023-09-22T14:57:25+09:00 The term ‘Trading technologies’ collectively refers to the various tools, platforms, software, and systems used by financial professionals to execute and manage trades in the financial markets. Over the years these technologies evolved significantly and have become very sophisticated and play a cruc… <p>The term ‘Trading technologies’ collectively refers to the various tools, platforms, software, and systems used by financial professionals to execute and manage trades in the financial markets.</p> <p>Over the years these technologies evolved significantly and have become very sophisticated and play a crucial role in modern financial markets.</p> <p>Some of the key components and aspects of trading technologies are:</p> <ol> <li><p><strong>Electronic Trading Platform</strong>: Highly customizable software applications that allows traders to place and execute trade, check P&amp;L, balance and manage risk. These platforms can be configured per client requirements and are available in desktop, web-based and in mobile versions. Examples include TradeStation, TD Ameritrade, and Robinhood for retail traders, and Bloomberg Terminal, Reuters Eikon for professionals.</p></li> <li><p><strong>Algorithmic Trading</strong>: Algorithmic Trading or algo trading refers to the use of software code to create algorithms which in turn automate the trading strategy. These algorithms can analyze market data and execute orders at high speeds depending on configurable conditions and other factors like price movement, trends, volume etc. These are often used by institutional investors and hedge funds to optimize trading. Some of the examples of algorithmic trading includes:</p> <ol type="a"> <li>Trend Following Algorithms: Primary aim is to check price trends and buy and sell assets based on it. RSI and MA crossovers follow this pattern.</li> <li>Predictive Models: They try to predict future price movements based on historical data, market sentiment, news, and others.</li> <li>Volatility Trading: These algorithms aim to profit from changes in market volatility.</li> </ol> </li> <li><p><strong>High Frequency Trading</strong>: Specialized trading setup that relies on software and hardware acceleration to execute large number of orders in milliseconds or microseconds. Hardware acceleration is a solution where compute-intensive workloads are being offloaded to <strong>GPU’s or FPGAs</strong>. <br> <strong>What are FPGAs</strong>: Stands for <strong>Field Programmable Gate Arrays</strong>. In short, it’s a chip that contains a million or more logic blocks being repeated throughout the silicon. Each of these logic blocks called lookup tables (LUTs) includes basic logical operation such as Boolean AND, OR, NAND or XOR. The advantage of using FPGA is that it offers hardware accelerated solution for co-located Direct Market Access and pre-trade risk management.</p></li> <li><p><strong>Order Management Systems (OMS)</strong>: Is a set of software applications or tools used by financial institutions and other trading organizations to streamline and automate the entire order processing lifecycle in terms of Order Entry, Order Routing, Execution, Portfolio Management, Risk Management and Compliance Reporting.</p></li> </ol> <h3 id="Types-of-Trading-Venues">Types of Trading Venues</h3> <p>Trading Venues are locations that allow transactions of financial instruments. Based on the financial instruments being handled and their structure, venue can be broadly categorized into Stock Exchange, Commodity Exchange, Bond/Derivative Market.</p> <p>In the context of trading venues and particularly in the realm of equities (stock) two terms are very important – <strong>Lit Market</strong> and <strong>Dark Market</strong>.</p> <ol> <li><strong>Lit Market/Venue</strong>: In general, all the publicly trading exchanges like JPX, NYSE, LSE etc. come under this category. These exchanges share the same characteristics as: <ol type="a"> <li><strong>Transparency</strong>: All orders and trade information are visible to market participants in real time.</li> <li><strong>Price Discovery</strong>: This is the central function of a marketplace and strikes to find the spot price or the proper price of an asset, security, commodity, or currency. In simpler terms it is where a buyer and a seller agree on a price and a transaction occurs.</li> <li><strong>Highly Regulated</strong>: Public exchanges are subjected to rigorous regulatory processes to ensure fair and transparent trading practices among all the participants.</li> </ol> </li> <li><strong>Dark Market/Venue</strong>: Also known as ‘Dark Pool’ and are financial exchange/ hubs which are privately organized and facilitate trading of financial securities. Dark pools are in stark contrast to public financial exchange / lit market, where there is a high degree of regulation. Some of the well-known dark pools are CBOE Kai-X, Instinet BlackCross and Liquidnet. Below are some of the characteristics of dark pools: <ol type="a"> <li><strong>Private Trading</strong>: Dark pools allow for trading execution away from the spotlight of public markets. Order information along with participant information is not displayed to the public and hence reduced transparency.</li> <li><strong>Avoidance of Price Devaluation</strong>: Being private in nature attracts institutional investors to use dark venues to execute large orders with reduced market impact. If the similar had been executed in public exchange, then there would have been high price fluctuation.</li> <li><strong>Price Improvement</strong>: Dark pools offer price improvement opportunities where trades can be executed at Mid (mid-price of BBO), Near (Best Bid Price for Buy and Best Ask Price for Sell) and Far (Best Bid Price for Sell and Best Ask Price for Buy).</li> </ol> </li> </ol> <h3 id="Types-of-Financial-Protocols">Types of Financial Protocols</h3> <p>To facilitate trading, communication and data exchange the widely used protocols are FIX, OUCH and ITCH.</p> <h2 id="FIX-Financial-Information-Exchange">FIX (Financial Information Exchange)</h2> <p>FIX 4.2 is the well-known version of the protocol used by traders, brokers and institutions enabling exchange of information related to trading of securities like stock, bond, FX. FIX message format is a tagged, delimited ASCII format and each message is composed of a delimited series of unordered tag/value pairs.</p> <p> General message format is composed of the standard header followed by the body followed by the standard trailer.</p> <p> Below is an example of a FIX message for <strong>Buy 100 quantity of Monex at limit price</strong>.</p> <blockquote><p>8=FIX.4.2|35=D|34=1|49=SenderCompID|56=Broker|55=8698|54=1|38=100|44=570.00|40=1|10=007|</p></blockquote> <p> <strong>8=FIX.4.2</strong>: Indicates the FIX protocol version. <br> <strong>35=D</strong>: New Order - Single (D) message.<br> <strong>34=1</strong>: Unique message sequence number.<br> <strong>49=SenderCompID</strong>: Sending firm's identifier.<br> <strong>56=Broker</strong>: The broker's identifier.<br> <strong>55=8698</strong>: The symbol for Monex<br> <strong>54=1</strong>: Buy order.<br> <strong>38=100</strong>: Quantity of shares to buy.<br> <strong>44=570.00</strong>: Limit price.<br> <strong>40=1</strong>: Order type (Limit Order).<br> <strong>10=007</strong>: Checksum for message integrity.<br></p> <p>Some of the features of FIX 4.2 are:</p> <ol type="a"> <li> Message Based: Uses a specific structure to interchange trade messages between participants. These messages included order execution, trade confirmations, market data etc.</li> <li> Customizable: FIX is highly customizable and allows us to define custom messages to meet certain trading needs. For example, Tags from 5000 are used for custom messages.</li> <li> Acceptability: Used globally across financial institutions allowing seamless communication. Further details of FIX can be found here - <a href="https://www.fixtrading.org/standards/">https://www.fixtrading.org/standards/</a></li> </ol> <h2 id="ITCH-Incremental-Trading-Consolidated-Feed">ITCH (Incremental Trading Consolidated Feed)</h2> <p>The ITCH Protocol is widely used today, on both Nasdaq owned liquidity pools/exchanges, and other non-Nasdaq venues that use the Nasdaq OMX Genium INET and Nasdaq Financial Framework (NFF) exchange technology platforms. ITCH is binary protocol and used for disseminating detailed full-depth, order-level market data messages with ultra-low latency.</p> <p>ITCH Protocol usually defines the business-level message formats only and does not include a transport-level protocol. Messages are delivered using a lower-level transport protocol that takes care of sequencing, re-transmission, and delivery guarantees (e.g., SoupBinTCP or MoldUDP64).</p> <p>ITCH covers the following areas:</p> <ol type="a"> <li> Order lifecycle events (added, executed, canceled, deleted, replaced)</li> <li> Trades (provide execution details for match events involving non-displayable order types)</li> <li> Security definitions and statuses</li> <li> Market events and data feed events</li> </ol> <p>Below is an example of ITCH Binary message (in hexadecimal format)</p> <p>0x410000000000000000000003ea00000000000500cd4200000001000000000000000100000000</p> <table> <thead> <tr> <th style="text-align:left;"><strong>Field name</strong></th> <th style="text-align:left;"><strong>Offset</strong></th> <th style="text-align:left;"><strong>Length</strong></th> <th style="text-align:left;"><strong>Format</strong></th> <th style="text-align:left;"><strong>Binary</strong></th> <th style="text-align:left;"><strong>Decoded</strong></th> <th style="text-align:left;"><strong>Notes</strong></th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"><strong>Message Type</strong></td> <td style="text-align:left;">0</td> <td style="text-align:left;">1</td> <td style="text-align:left;">Alphanumeric</td> <td style="text-align:left;">0x41</td> <td style="text-align:left;">A</td> <td style="text-align:left;">Add Order message</td> </tr> <tr> <td style="text-align:left;"><strong>Timestamp</strong></td> <td style="text-align:left;">1</td> <td style="text-align:left;">8</td> <td style="text-align:left;">Numeric</td> <td style="text-align:left;">0x0000000000000000</td> <td style="text-align:left;">0</td> <td style="text-align:left;">Nanoseconds</td> </tr> <tr> <td style="text-align:left;"><strong>Order Reference Number</strong></td> <td style="text-align:left;">9</td> <td style="text-align:left;">4</td> <td style="text-align:left;">Numeric</td> <td style="text-align:left;">0x000003EA</td> <td style="text-align:left;">1002</td> <td style="text-align:left;"> </td> </tr> <tr> <td style="text-align:left;"><strong>Transaction ID</strong></td> <td style="text-align:left;">13</td> <td style="text-align:left;">4</td> <td style="text-align:left;">Numeric</td> <td style="text-align:left;">0x00000000</td> <td style="text-align:left;">0</td> <td style="text-align:left;"> </td> </tr> <tr> <td style="text-align:left;"><strong>Order book ID</strong></td> <td style="text-align:left;">17</td> <td style="text-align:left;">4</td> <td style="text-align:left;">Numeric</td> <td style="text-align:left;">0x000500CD</td> <td style="text-align:left;">327885</td> <td style="text-align:left;"> </td> </tr> <tr> <td style="text-align:left;"><strong>Side</strong></td> <td style="text-align:left;">21</td> <td style="text-align:left;">1</td> <td style="text-align:left;">Alphanumeric</td> <td style="text-align:left;">0x42</td> <td style="text-align:left;">B</td> <td style="text-align:left;">“B” = Buy order<br>“S” = Sell order</td> </tr> <tr> <td style="text-align:left;"><strong>Quantity</strong></td> <td style="text-align:left;">22</td> <td style="text-align:left;">4</td> <td style="text-align:left;">Numeric</td> <td style="text-align:left;">0x00000001</td> <td style="text-align:left;">1</td> <td style="text-align:left;"> </td> </tr> <tr> <td style="text-align:left;"><strong>Price</strong></td> <td style="text-align:left;">26</td> <td style="text-align:left;">8</td> <td style="text-align:left;">Price</td> <td style="text-align:left;">0x0000000000000001</td> <td style="text-align:left;">1</td> <td style="text-align:left;"> </td> </tr> <tr> <td style="text-align:left;"><strong>Yield</strong></td> <td style="text-align:left;">34</td> <td style="text-align:left;">4</td> <td style="text-align:left;">Price</td> <td style="text-align:left;">0x00000000</td> <td style="text-align:left;">0</td> <td></td> </tr> </tbody> </table> <p>Another example of ITCH market data message:</p> <p>T|8698|100|570.00|20230918093015000</p> <p><strong>T</strong>: Indicates a trade message. <br> <strong>8698</strong>: The symbol for the stock.<br> <strong>100</strong>: Quantity of shares traded.<br> <strong>570.00</strong>: Trade price.<br> <strong>20230918093015000</strong>: Timestamp (in microseconds) of the trade.<br></p> <h2 id="OUCH">OUCH</h2> <p>This protocol is used for order entry and execution. It is designed to offer the maximum possible performance at the cost of flexibility and ease of use. The messages are binary encoded in OUCH Protocol, which means that all numeric values are represented as binary values.</p> <p>OUCH offers the following advantages:</p> <ol> <li>Efficiency and low latency</li> <li>High level of performance</li> <li>Delivery of real-time execution information (DROP)</li> </ol> <p>Below is the example of an OUCH message for<strong> Buy order, AAPL, quantity 5,000 at $151</strong></p> <p>Enter Order Message – Inbound message sent by user</p> <table> <thead> <tr> <th style="text-align:left;"><strong>Field Name</strong></th> <th style="text-align:left;"><strong>Length</strong></th> <th style="text-align:left;"><strong>Format</strong></th> <th style="text-align:left;"><strong>Example</strong></th> </tr> </thead> <tbody> <tr> <td style="text-align:left;">Message Type</td> <td style="text-align:left;">1</td> <td style="text-align:left;">Alpha</td> <td style="text-align:left;">“O” (Enter Order Message)</td> </tr> <tr> <td style="text-align:left;">Side</td> <td style="text-align:left;">1</td> <td style="text-align:left;">Alpha</td> <td style="text-align:left;">B</td> </tr> <tr> <td style="text-align:left;">Quantity</td> <td style="text-align:left;">8</td> <td style="text-align:left;">Numeric</td> <td style="text-align:left;">5000</td> </tr> <tr> <td style="text-align:left;">Price</td> <td style="text-align:left;">4</td> <td style="text-align:left;">Price</td> <td style="text-align:left;">151</td> </tr> <tr> <td style="text-align:left;">Order Book ID</td> <td style="text-align:left;">4</td> <td style="text-align:left;">Numeric</td> <td style="text-align:left;">AAPL (translated value)</td> </tr> </tbody> </table> <p>Collectively, these protocols, along with sophisticated technologies and trading strategies, have revolutionized financial markets. They have enabled automation, algorithmic trading, and complex strategies that were once unimaginable. Moreover, these protocols have made markets more accessible and interconnected, fostering global participation and liquidity.</p> <p>Anish Basu - Senior Engineer</p> nzkkun robocopy でサブフォルダ―『に』コピーしてみると hatenablog://entry/820878482956739133 2023-09-08T12:59:52+09:00 2023-09-08T12:59:52+09:00 こんにちは システム開発一部の寺田です。 Windows でフォルダのバックアップなど大量のファイルコピーを行うとき robocopy ⧉ が便利です。 オプションを指定することでフォルダの同期やコピーの再試行ができます。 使用例 source フォルダにファイルを作成し、サブフォルダごとコピー( robocopy /e ) :prepare cd /d c:\ mkdir source source\sub echo test>source\test.txt echo test2>source\sub\test2.txt dir /s /b source :copy robocopy /e … <div style="text-align: center"> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/msterada/20230828/20230828093005.jpg" width="427" height="640" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> </div> <p>こんにちは システム開発一部の寺田です。</p> <p>Windows でフォルダのバックアップなど大量のファイルコピーを行うとき <a href="https://learn.microsoft.com/ja-jp/windows-server/administration/windows-commands/robocopy">robocopy &#10697;</a> が便利です。<br/> オプションを指定することでフォルダの同期やコピーの再試行ができます。</p> <h4 id="使用例">使用例</h4> <p><code>source</code> フォルダにファイルを作成し、サブフォルダごとコピー( <code>robocopy /e</code> )</p> <pre class="code lang-dosbatch" data-lang="dosbatch" data-unlink><span class="synStatement">:prepare</span> <span class="synIdentifier">cd</span> <span class="synSpecial">/d</span> c:\ <span class="synIdentifier">mkdir</span> source source\sub <span class="synIdentifier">echo</span><span class="synConstant"> test</span>&gt;source\test.txt <span class="synIdentifier">echo</span><span class="synConstant"> test2</span>&gt;source\sub\test2.txt <span class="synIdentifier">dir</span> <span class="synSpecial">/s</span> <span class="synSpecial">/b</span> source <span class="synStatement">:copy</span> robocopy <span class="synSpecial">/e</span> source target </pre> <p><code>target</code> フォルダが新たに作成され <code>source</code> と同じ状態になる</p> <pre class="code lang-dosbatch" data-lang="dosbatch" data-unlink>c:\&gt;<span class="synIdentifier">dir</span> <span class="synSpecial">/s</span> <span class="synSpecial">/b</span> source target c:\source\sub c:\source\test.txt c:\source\sub\test2.txt c:\target\sub c:\target\test.txt c:\target\sub\test2.txt </pre> <h4 id="サブフォルダにコピー">サブフォルダ『に』コピー</h4> <p><code>コピー先</code> に <code>コピー元のサブフォルダ</code> を指定すると</p> <p><strong>注意</strong> 以下のコマンドは必ず <code>/256</code> オプションを付けて実行してください。</p> <pre class="code lang-dosbatch" data-lang="dosbatch" data-unlink>robocopy <span class="synSpecial">/e</span> /<span class="synConstant">256</span> source source\sub\target </pre> <p>実行結果</p> <pre class="code lang-dosbatch" data-lang="dosbatch" data-unlink>c:\&gt;robocopy <span class="synSpecial">/e</span> /<span class="synConstant">256</span> source source\sub\target 新しいディレクトリ <span class="synConstant">1</span> c:\source\ 100% 新しいファイル <span class="synConstant">6</span> test.txt 新しいディレクトリ <span class="synConstant">1</span> c:\source\sub\ 100% 新しいファイル <span class="synConstant">7</span> test2.txt 新しいディレクトリ <span class="synConstant">1</span> c:\source\sub\target\ 100% 新しいファイル <span class="synConstant">6</span> test.txt 新しいディレクトリ <span class="synConstant">1</span> c:\source\sub\target\sub\ 100% 新しいファイル <span class="synConstant">7</span> test2.txt 新しいディレクトリ <span class="synConstant">1</span> c:\source\sub\target\sub\target\ 100% 新しいファイル <span class="synConstant">6</span> test.txt 新しいディレクトリ <span class="synConstant">1</span> c:\source\sub\target\sub\target\sub\ 100% 新しいファイル <span class="synConstant">7</span> test2.txt 新しいディレクトリ <span class="synConstant">1</span> c:\source\sub\target\sub\target\sub\target\ 100% 新しいファイル <span class="synConstant">6</span> test.txt (中略) 新しいディレクトリ <span class="synConstant">1</span> c:\source\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\ 100% 新しいファイル <span class="synConstant">7</span> test2.txt 新しいディレクトリ <span class="synConstant">1</span> c:\source\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\ 2023/<span class="synConstant">08</span>/<span class="synConstant">28</span> <span class="synConstant">10</span>:<span class="synConstant">32</span>:<span class="synConstant">50</span> エラー <span class="synConstant">206</span> (<span class="synConstant">0x000000CE</span>) コピー先ディレクトリを作成しています c:\source\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\sub\target\ ファイル名または拡張子が長すぎます。 30 秒間待機しています... </pre> <p>次々と <code>sub</code> と <code>target</code> フォルダが作成され無限ループしてしまいます。<br/> <code>/256</code> オプションを付けないと、Windows または NTFS が許容する限界のパス長までコピーが行われます。</p> <h4 id="考察">考察</h4> <p>robocopy の内部処理において、<br/> あらかじめコピーするファイルリストを作成するのではなく、<br/> コピー元のフォルダから再帰的に順次コピーを行っていると推察されます。</p> <p>単純にサブフォルダを対象したときだけではなく、<br/> 恣意的な例になりますが、マウントした別ドライブなどをコピー先にしたときでも発生します。</p> <pre class="code lang-dosbatch" data-lang="dosbatch" data-unlink><span class="synStatement">:mount</span> <span class="synIdentifier">subst</span> s: c:\source\sub robocopy <span class="synSpecial">/e</span> /<span class="synConstant">256</span> source s:\target <span class="synStatement">:unmount</span> <span class="synIdentifier">subst</span> <span class="synSpecial">/d</span> s: </pre> <h4 id="対処方法1---パスの比較">対処方法1 - パスの比較</h4> <p>コピー先がサブフォルダかどうかの判定はパスでの比較が容易です。<br/> ただし、コピー元やコピー先には複数のパス形式が入力できるため、<br/> 相対パスや絶対パス、パスの区切り文字( <code>\</code> or <code>/</code> )の統一に <code>pushd</code> を使います。</p> <pre class="code lang-dosbatch" data-lang="dosbatch" data-unlink><span class="synStatement">:input</span> <span class="synIdentifier">set from</span><span class="synStatement">=</span><span class="synConstant">&quot;c:\source&quot;</span> <span class="synIdentifier">set to</span><span class="synStatement">=</span><span class="synConstant">&quot;source/sub/target&quot;</span> <span class="synStatement">:normalize</span> <span class="synIdentifier">pushd</span> <span class="synIdentifier">%from%</span> <span class="synIdentifier">set from</span><span class="synStatement">=</span><span class="synConstant">&quot;</span><span class="synIdentifier">%cd%</span><span class="synConstant">&quot;</span> <span class="synIdentifier">popd</span> <span class="synIdentifier">pushd</span> <span class="synIdentifier">%to%</span> <span class="synIdentifier">set to</span><span class="synStatement">=</span><span class="synConstant">&quot;</span><span class="synIdentifier">%cd%</span><span class="synConstant">&quot;</span> <span class="synIdentifier">popd</span> <span class="synIdentifier">echo</span><span class="synConstant"> </span><span class="synIdentifier">%from%</span><span class="synConstant"> </span><span class="synIdentifier">%to%</span> <span class="synStatement">:compare</span> <span class="synIdentifier">echo</span><span class="synConstant"> </span><span class="synIdentifier">%to%</span><span class="synConstant"> </span>| <span class="synIdentifier">find</span> <span class="synIdentifier">%from%</span> &gt; nul <span class="synStatement">if </span><span class="synIdentifier">%errorlevel%</span><span class="synStatement">==</span><span class="synConstant">0</span> ( <span class="synIdentifier">echo</span><span class="synConstant"> </span><span class="synIdentifier">%to%</span><span class="synConstant"> は </span><span class="synIdentifier">%from%</span><span class="synConstant"> のサブフォルダです。</span> ) <span class="synStatement">else</span> ( <span class="synIdentifier">echo</span><span class="synConstant"> </span><span class="synIdentifier">%to%</span><span class="synConstant"> は </span><span class="synIdentifier">%from%</span><span class="synConstant"> のサブフォルダではありません。</span> ) </pre> <p>実行結果</p> <pre class="code lang-dosbatch" data-lang="dosbatch" data-unlink><span class="synConstant">&quot;c:\source\sub\target&quot;</span> は <span class="synConstant">&quot;c:\source&quot;</span> のサブフォルダです。 </pre> <h4 id="対処方法2---File-ID-を取得">対処方法2 - File ID を取得</h4> <p>ファイルやフォルダが厳密に一致しているかどうかは File ID で判断できます。<br/> <a href="https://learn.microsoft.com/ja-jp/windows-server/administration/windows-commands/fsutil-file">fsutil file &#10697;</a> で File ID の取得が可能です。</p> <p>直接サブフォルダであるか判定することはできないため、<br/> コピー元のサブフォルダを再帰的に巡回し、コピー先と File ID が一致しないことを確認します。<br/> ※ <code>setlocal</code> を使っているため bat ファイルからの実行が必要です。</p> <pre class="code lang-dosbatch" data-lang="dosbatch" data-unlink>@<span class="synIdentifier">echo</span><span class="synStatement"> off</span> <span class="synStatement">:prepare</span> <span class="synIdentifier">setlocal</span> enabledelayedexpansion <span class="synIdentifier">subst</span> s: c:\source\sub <span class="synStatement">:input</span> <span class="synIdentifier">set from</span><span class="synStatement">=</span><span class="synConstant">&quot;c:\source&quot;</span> <span class="synIdentifier">set to</span><span class="synStatement">=</span><span class="synConstant">&quot;s:\target&quot;</span> <span class="synStatement">for</span> <span class="synSpecial">/f</span> <span class="synConstant">&quot;DELIMS=&quot;</span> <span class="synSpecial">%%</span>A in (<span class="synPreProc">'fsutil file queryfileid </span><span class="synIdentifier">%to%</span><span class="synPreProc">'</span>) do <span class="synIdentifier">set fileid</span><span class="synStatement">=</span><span class="synSpecial">%%</span>A <span class="synIdentifier">echo</span><span class="synConstant"> id:[</span><span class="synIdentifier">%fileid:~</span><span class="synConstant">10</span><span class="synIdentifier">,</span><span class="synConstant">34</span><span class="synIdentifier">%</span><span class="synConstant">] path:</span><span class="synIdentifier">%to%</span> <span class="synStatement">for</span> <span class="synSpecial">/d</span> <span class="synSpecial">/r</span> <span class="synIdentifier">%from%</span> <span class="synSpecial">%%</span>d in (*) do ( <span class="synStatement">for</span> <span class="synSpecial">/f</span> <span class="synConstant">&quot;DELIMS=&quot;</span> <span class="synSpecial">%%</span>B in (<span class="synPreProc">'fsutil file queryfileid </span><span class="synConstant">&quot;</span><span class="synSpecial">%%</span><span class="synConstant">d&quot;</span><span class="synPreProc">'</span>) do <span class="synIdentifier">set fileid2</span><span class="synStatement">=</span><span class="synSpecial">%%</span>B <span class="synIdentifier">echo</span><span class="synConstant"> id:[</span><span class="synIdentifier">!fileid2:~</span><span class="synConstant">10</span><span class="synIdentifier">,</span><span class="synConstant">34</span><span class="synIdentifier">!</span><span class="synConstant">] path:&quot;</span><span class="synSpecial">%%</span><span class="synConstant">d&quot;</span> <span class="synStatement"> :compare</span> <span class="synStatement">if </span><span class="synIdentifier">!fileid!</span> <span class="synStatement">==</span> <span class="synIdentifier">!fileid2!</span> ( <span class="synIdentifier">echo</span><span class="synConstant"> </span><span class="synIdentifier">%to%</span><span class="synConstant"> は </span><span class="synIdentifier">%from%</span><span class="synConstant"> のサブフォルダ[</span><span class="synSpecial">%%</span><span class="synConstant">d]です。</span> <span class="synStatement">goto :end</span> ) ) <span class="synIdentifier">echo</span><span class="synConstant"> </span><span class="synIdentifier">%to%</span><span class="synConstant"> は </span><span class="synIdentifier">%from%</span><span class="synConstant"> のサブフォルダではありません。</span> <span class="synStatement">:end</span> <span class="synIdentifier">subst</span> <span class="synSpecial">/d</span> s: <span class="synIdentifier">endlocal</span> <span class="synIdentifier">pause</span> </pre> <p>実行結果</p> <pre class="code lang-dosbatch" data-lang="dosbatch" data-unlink>id:[0x0000000000000000013300000005c89e] <span class="synIdentifier">path</span>:<span class="synConstant">&quot;s:\target&quot;</span> id:[0x00000000000000000160000000012d61] <span class="synIdentifier">path</span>:<span class="synConstant">&quot;c:\source\sub&quot;</span> id:[0x0000000000000000013300000005c89e] <span class="synIdentifier">path</span>:<span class="synConstant">&quot;c:\source\sub\target&quot;</span> <span class="synConstant">&quot;s:\target&quot;</span> は <span class="synConstant">&quot;c:\source&quot;</span> のサブフォルダ[c:\source\sub\target]です。 </pre> <p>異なるドライブレターのファイルについても同一の File ID であることが確認ができます。</p> <h4 id="まとめ">まとめ</h4> <p>robocopy のコピー先に特殊なパスを入れたときのご紹介でした。<br/> パスのバリエーションが多く、正しいチェックは難しいのですが、<br/> いくつかの試行錯誤したチェック方法を検討しました。</p> <p>Windows バッチは普段の開発ではあまり出番はありませんが、<br/> 開発環境の構築や、定型的な自動処理などで役に立ちます。</p> <p>既存のシステム開発の効率を向上したい。<br/> 新しいサービスを導入してみたい、そのようなメンバーを募集しております。</p> <p>マネックスグループの採用にご興味のある方は、ぜひ以下募集をご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.monexgroup.jp%2Frecruit" title="マネックスグループ株式会社採用" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.monexgroup.jp/recruit">www.monexgroup.jp</a></cite></p> msterada 消えるStackTrace hatenablog://entry/820878482960360264 2023-08-25T16:37:16+09:00 2023-08-25T17:16:37+09:00 こんにちは マネックスラボのMです。 ラボではJavaを使用したシステムが多いです 先日とある不具合が発生してしまいまして ログを調査していたところ この事象が発生していましたので紹介したいと思います。 事象 Javaのログで例外が出たとなるとこのようなStackTraceの出力を期待するかと思います。 2023-08-25 10:00:00.000 ERROR 11111 --- [http-nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherSer… <p>こんにちは マネックスラボのMです。</p> <p>ラボではJavaを使用したシステムが多いです<br/> 先日とある不具合が発生してしまいまして<br/> ログを調査していたところ <br/> この事象が発生していましたので紹介したいと思います。</p> <h2 id="事象">事象</h2> <p>Javaのログで例外が出たとなるとこのようなStackTraceの出力を期待するかと思います。</p> <pre class="code" data-lang="" data-unlink>2023-08-25 10:00:00.000 ERROR 11111 --- [http-nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause java.lang.NullPointerException: null at abc.logic.AbcLogic.addParams(AbcLogic.java:111) at abc.controller.AbcController.getItems(AbcController.java:222) at abc.controller.AbcController.getGroups(AbcController.java:333) at abc.controller.AbcController.index(AbcController.java:444) ...</pre> <p>このようにStackTraceが出力されることで原因行の特定ができるのですが<br/> 今回紹介する事象が発生すると以下のようにStackTrace部分が出なくなってしまいます</p> <pre class="code" data-lang="" data-unlink>2023-08-25 10:00:00.000 ERROR 11111 --- [http-nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause</pre> <h2 id="何が起きているか">何が起きているか</h2> <p>どうやらJava7頃からこの機能が実装されたようですが<br/> 高速パスと言われる、<strong>ループ処理</strong>や、<strong>頻繁に呼び出されるメソッド</strong> の中で<br/> <strong>特定の例外</strong>(NullPointerException や ArrayIndexOutOfBoundsException など)が発生すると<br/> StackTraceの出力を省略するそうです<br/> これはStackTraceの取得に負荷がかかるためパフォーマンスを改善する目的のものとのことです</p> <h2 id="どのくらい省略されるのか">どのくらい省略されるのか</h2> <p>今わかっていることは以下です</p> <ul> <li>一度省略が発生すると、JVM起動中はずっと省略される</li> <li>JVMを再起動すると(おそらく)省略前の状態になる=しばらく出力される</li> <li>省略されはじめる例外の発生回数は決まっていない? <ul> <li>検証したところ、同じロジックでも2回で省略される場合もあれば、2万回で省略される場合もある</li> </ul> </li> </ul> <h2 id="対応方法はあるか">対応方法はあるか</h2> <pre class="code" data-lang="" data-unlink>-XX:-OmitStackTraceInFastThrow</pre> <p>上記の起動オプションをJVMに与えて起動することで省略されなくなります<br/> ただし、元々パフォーマンスを改善する目的のものなので注意が必要とのことです<br/> が、実際にはこの機能に該当するような例外がパフォーマンスに影響するほど頻発する<br/> 状態で機能をリリースするようなことはないんじゃないかと個人的には思います</p> MNXM StepFunctionsで実行したECS Runtaskをトレースする。 hatenablog://entry/820878482958210893 2023-08-18T12:00:00+09:00 2023-08-18T12:00:24+09:00 StepFunctionsで実行したECS Runtaskをトレースします。Lambdaのように簡単にはいかずはまりました。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230814/20230814105941.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>マネックス証券 システム管理部のHです。<br></p> <h3 id="はじめに">はじめに</h3> <p>Step Functionsはワークフローのサービスで、連続する複数のジョブ実行として現在私が関わっているプロジェクトでは使用しています。 Step FunctionsはX-Rayと連携されるため、ステートマシン内で実行されるジョブのトレースも取得できるのですが、ややはまったところがあったため書いていきます。 X-Rayについては下記ブログでも触れています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2021%2F09%2F03%2F170000" title="AWS X-Rayを使ってAPIをトレースしよう!! - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2021/09/03/170000">blog.tech-monex.com</a></cite></p> <h3 id="やりたいこと困っていること">やりたいこと、困っていること</h3> <p>上記に書いた通り、Step FunctionsはX-Rayとも連携されており、Step Function自体は設定でチェックボックスをオンにするだけで簡単にトレースを取ることができます。 Step Functionのステートマシン内で実行されるAWSリソースについても連携できるものがあり、Lambdaについてはこれも設定でチェックボックスをオンにするだけでX-Rayの画面でStep FunctionsとLambda合わせてトレースを確認することができます。<br> 一方でECS RuntaskについてもStep Functionsと連携を取りたいのですが、Step FunctionsとECS Runtaskの間でそれぞれX-Rayの設定をしても、連携されません・・・。</p> <h4 id="Step-FunctionsLambdaの場合">Step Functions+Lambdaの場合</h4> <p>上記の通り、それぞれ設定でチェックボックスをオンにするだけでトレースデータが連携されます。</p> <h5 id="Step-Functionsステートマシーン">Step Functionsステートマシーン</h5> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230814/20230814133815.png" width="255" height="437" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h5 id="Lambda関数">Lambda関数</h5> <p>単純なレスポンスを返す関数です。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">export</span> <span class="synStatement">const</span> handler = async (<span class="synStatement">event</span>) =&gt; <span class="synIdentifier">{</span> <span class="synComment">// </span><span class="synTodo">TODO</span><span class="synComment"> implement</span> <span class="synStatement">const</span> response = <span class="synIdentifier">{</span> statusCode: 200, body: JSON.stringify(<span class="synConstant">'こんにちはマネックス'</span>), <span class="synIdentifier">}</span>; <span class="synStatement">return</span> response; <span class="synIdentifier">}</span>; </pre> <h5 id="X-Rayトレースマップ">X-Rayトレースマップ</h5> <p>Step FunctionsステートマシンとLambdaのトレースが表示されています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230814/20230814134131.png" width="1200" height="629" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="Step-FunctionsECS-Runtaskの場合">Step Functions+ECS Runtaskの場合</h4> <h5 id="Step-Functionsステートマシーン-1">Step Functionsステートマシーン</h5> <p>Step Functionsでは設定でチェックボックスをオンにしています。一方ECS Runtaskについては特に設定しておりません。</p> <h5 id="ECS-Runtask">ECS Runtask</h5> <p>Runtask内で実行するSpring Batchアプリケーションについて設定します。<a href="https://blog.tech-monex.com/entry/2021/09/03/170000">ここ</a>を参考に設定します。</p> <ul> <li>バッチ設定クラス<br> @XRayEnabledのアノテーションを設定することで、X-Rayのトレースマップに「job」として表示されます。</li> </ul> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@Slf4j</span> <span class="synPreProc">@XRayEnabled</span> <span class="synPreProc">@Configuration</span> <span class="synType">public</span> <span class="synType">class</span> BatchConfiguration { <span class="synPreProc">@Bean</span> <span class="synType">public</span> Job job(JobRepository jobRepository, PlatformTransactionManager txManager) { Step step = <span class="synStatement">new</span> StepBuilder(<span class="synConstant">&quot;Step1&quot;</span>, jobRepository) .tasklet(<span class="synStatement">new</span> TestTasklet(), txManager) .build(); log.info(<span class="synConstant">&quot;Execute Step&quot;</span>); Job job = <span class="synStatement">new</span> JobBuilder(<span class="synConstant">&quot;TestJob&quot;</span>, jobRepository) .start(step) .build(); log.info(<span class="synConstant">&quot;Executed Step: &quot;</span> + job.getName()); <span class="synStatement">return</span> job; } } </pre> <ul> <li>タスクレット<br> タスクレットは自動ではトレースが出力されないので、明示的に<a href="https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-sdk-java-subsegments.html">サブセグメント</a>を設定します。</li> </ul> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@Slf4j</span> <span class="synType">public</span> <span class="synType">class</span> TestTasklet <span class="synType">implements</span> Tasklet { <span class="synPreProc">@Override</span> <span class="synType">public</span> RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) <span class="synType">throws</span> Exception { Subsegment subsegment = AWSXRay.beginSubsegment(<span class="synConstant">&quot;TestTasklet::execute&quot;</span>); log.info(<span class="synConstant">&quot;TestTasklet::execute&quot;</span>); Thread.sleep(EnvUtil.getSleepTime()); <span class="synComment">// トレースを見やすくするため数秒間停止させる</span> subsegment.end(); <span class="synStatement">return</span> RepeatStatus.FINISHED; } } </pre> <ul> <li><p>X-Rayトレースマップ<br> Step FunctionsとECS Runtaskは別々のトレースIDとなるため、1画面ですべての情報は表示できませんでした。1画面で見れないとそれぞれで画面を開かないといけないため、不便です。⇐ここが今回困っているところ。</p> <ul> <li><p>Step Functionsステートマシン<br> ECS Runtaskのトレースは表示されていますが、ECS内のトレースが表示されません。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230814/20230814150324.png" width="1200" height="708" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p></li> <li><p>ECS Runtask<br> Spring Batchが実行しているジョブ、タスクレットの情報が表示されます。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230814/20230814150518.png" width="1200" height="606" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p></li> </ul> </li> </ul> <h3 id="どうやって実現するか考える">どうやって実現するか考える</h3> <p><a href="https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/concepts-xray-tracing.html">X-RayとStep Functionsのデベロッパーガイド</a>を見ても、Lambdaのように簡単にはいかないようです。ECS Runtaskの場合にStep Functionsとトレースの画面が分かれてしまうのは、トレースIDがそれぞれで違うためです。ならばStep Functionsで実行しているステートマシーンのトレースIDをECS Runtaskに渡せればいけるのではないかと思いました。しかしどこからトレースIDが取得できるのだろうか。</p> <h3 id="実装してみる">実装してみる</h3> <p>トレースIDを取得する方法として、やや強引ですが、ECS Runtaskの前にLambdaを追加しました。<br> LambdaはHTTPヘッダーの中にトレースIDを持っており、それをECS Runtaskの入力で設定します。<br> Spring Batchでは、X-RayのSDKでトレースIDに環境変数から取得した値を設定します。</p> <h4 id="Step-Functionsステートマシン">Step Functionsステートマシン</h4> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230815/20230815134244.png" width="255" height="440" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="Lambda関数-1">Lambda関数</h4> <p>出力の中にトレースIDが含まれています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230815/20230815135545.png" width="759" height="476" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="ECS-Runtask-1">ECS Runtask</h4> <p>環境変数にトレースIDを設定します。</p> <ul> <li><p>パラメータ設定 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230815/20230815135821.png" width="623" height="320" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p></li> <li><p>実際の入力値 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230815/20230815135945.png" width="647" height="341" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p></li> <li><p>Spring Batchメインクラス<br> メインクラス内で環境変数からトレースIDを取得します。</p></li> </ul> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@Slf4j</span> <span class="synPreProc">@SpringBootApplication</span> <span class="synType">public</span> <span class="synType">class</span> EblogMainApplication { <span class="synType">public</span> <span class="synType">static</span> <span class="synType">final</span> String TRACE_NAME = <span class="synConstant">&quot;Eblog-202308&quot;</span>; <span class="synType">public</span> <span class="synType">static</span> <span class="synType">void</span> main(String[] args) { List&lt;String&gt; ids = getTraceIds(); <span class="synStatement">if</span> (ids.size() &gt; <span class="synConstant">0</span>) { TraceID traceId = TraceID.fromString(ids.get(<span class="synConstant">0</span>)); <span class="synComment">// rootの値がトレースID</span> AWSXRay.getGlobalRecorder().beginSegment(TRACE_NAME, traceId, <span class="synConstant">null</span>); } <span class="synStatement">else</span> { AWSXRay.getGlobalRecorder().beginSegment(TRACE_NAME); } SpringApplication.run(EblogMainApplication.<span class="synType">class</span>, args); AWSXRay.endSegment(); } <span class="synComment">// 環境変数から値を取得し、「;」「=」を区切り文字として分割してリストに代入する。</span> <span class="synComment">// root=1-xxxxxxxx-xxxxxxxx;parent=xxxxxxxx;sampled=1;lineage=xxxxxxxx:0</span> <span class="synType">private</span> <span class="synType">static</span> List&lt;String&gt; getTraceIds() { List&lt;String&gt; list = <span class="synStatement">new</span> ArrayList&lt;&gt;(); String envTraceIds = System.getenv(<span class="synConstant">&quot;TRACE_ID&quot;</span>); <span class="synStatement">if</span> (StringUtils.isEmpty(envTraceIds)) { <span class="synStatement">return</span> list; } log.info(<span class="synConstant">&quot;trace data: &quot;</span> + envTraceIds); String[] arr = envTraceIds.split(<span class="synConstant">&quot;;&quot;</span>); <span class="synStatement">if</span> (arr.length &gt;= <span class="synConstant">2</span>) { <span class="synStatement">for</span> (String tmpStr: arr) { String[] val = tmpStr.split(<span class="synConstant">&quot;=&quot;</span>); <span class="synStatement">if</span> (val.length &gt;= <span class="synConstant">2</span>) { list.add(val[<span class="synConstant">1</span>]); } } } <span class="synStatement">else</span> { <span class="synStatement">return</span> list; } <span class="synStatement">if</span> (list.size() &lt; <span class="synConstant">2</span>) { <span class="synStatement">return</span> <span class="synStatement">new</span> ArrayList&lt;&gt;(); } <span class="synStatement">return</span> list; } } </pre> <h3 id="確認してみる">確認してみる</h3> <p>Step Functionsのステートマシンを実行したトレースです。1画面内にECS Runtaskのトレースも含まれています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230815/20230815153003.png" width="1200" height="591" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>一見するとうまくいっているように見えます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230815/20230815154601.png" width="1096" height="580" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ですがトレースマップを見てみるとStep Functionsの部分とECS Runtaskの部分が分かれました。ECSの下にEblog-202308がついてほしかったのですが、そうなりませんでした・・・。<br> 理由はSpring Batchアプリケーション内で親のID(この場合ECSのID)を指定していないためです。 下記でnullを指定している部分に本来なら親のIDを指定するのですが、親のIDを取得することができなかったため仕方なくnullを指定しました。Step FunctionsとECS Runtaskの連携についてはAWSの今後に期待です。</p> <pre class="code lang-java" data-lang="java" data-unlink>AWSXRay.getGlobalRecorder().beginSegment(TRACE_NAME, traceId, <span class="synConstant">null</span>); </pre> <h3 id="おわりに">おわりに</h3> <p>課題はあるものの、ステートマシン実行のトレースについて、1画面内でStep FunctionsとECS Runtaskの確認をすることができました。ステートマシン内でLambdaとECS Runtask両方のジョブがある場合には利用できる方法かと思います。</p> <div style="text-align: center;"> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20200830/20200830084302.png" width="118" height="200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> </div> hamo2020 AWS認定資格を取得してみる hatenablog://entry/820878482956473530 2023-08-10T11:29:51+09:00 2023-08-10T11:29:51+09:00 以前の 投稿 でAWS認定資格を取得したことを書きましたが、AWSに関する学びは継続しています。 aws.amazon.com 現時点でプロフェッショナル2種を取得し、専門知識にチャレンジしている状態。今回はこのAWS認定試験について少し書いてみます。 必要性について 弊社のようにIT専業の企業ではない場合、クラウドに関わらず、エンジニア向け資格取得の必要性は薄いのではないか?という意見があります。 そうはいっても、クラウド環境が一般化し、またクラウド化案件も増えてきた昨今です。AWS認定資格の価値は、右肩上がりで増加している気がしています。ですので各レベルごとに、お勧めする理由などを簡単にま… <figure class="figure-image figure-image-fotolife mceNonEditable" title="AWS"> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/toshio_yamashita/20230808/20230808181652.jpg" width="640" height="427" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> </figure> <p>以前の <a href="https://blog.tech-monex.com/entry/2022/04/11/084554">投稿</a> でAWS認定資格を取得したことを書きましたが、AWSに関する学びは継続しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fcertification%2F" title="AWS 認定 – AWS クラウドコンピューティング認定プログラム | AWS" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://aws.amazon.com/jp/certification/">aws.amazon.com</a></cite></p> <p>現時点でプロフェッショナル2種を取得し、専門知識にチャレンジしている状態。今回はこのAWS認定試験について少し書いてみます。</p> <h3 id="必要性について">必要性について</h3> <p>弊社のようにIT専業の企業ではない場合、クラウドに関わらず、エンジニア向け資格取得の必要性は薄いのではないか?という意見があります。</p> <p>そうはいっても、クラウド環境が一般化し、またクラウド化案件も増えてきた昨今です。AWS認定資格の価値は、右肩上がりで増加している気がしています。ですので各レベルごとに、お勧めする理由などを簡単にまとめてみました。</p> <h4 id="ベーシック">ベーシック</h4> <p>AWS認定だと FOUNDATIONAL レベルの「AWS Certified Cloud Practitioner (CLF)」が該当します。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fcertification%2Fcertified-cloud-practitioner%2F" title="AWS Certified Cloud Practitioner 認定 | AWS 認定 | AWS" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://aws.amazon.com/jp/certification/certified-cloud-practitioner/">aws.amazon.com</a></cite></p> <p>「AWS クラウドの基礎的な理解を目的とした知識ベースの認定」と説明されています。</p> <p>こちら最初に取得するのに最適なAWS認定資格とおもわれます。用語やサービスについて幅広く理解できますので、AWS関連のいろんな話についていけるようになります。クラウド、AWS どちらにも独特の用語、概念がありますからね。</p> <p>既に詳しい方は以下のアソシエイトから取得しても良いのですが、AWS認定試験では合格するたびに試験の半額クーポンを取得できます。なので一番安いこの資格を取得してから取得しても、それほど費用は増加しません。また試験形式などに慣れる意味でも、受験しておくのは悪くないです。</p> <p>またAWS認定試験を受ける予定がなくても、AWS環境を利用している方であれば、CLFの試験対策本を1冊読むだけで、いろいろ学べて役に立つかとおもいます。</p> <h4 id="アソシエイト">アソシエイト</h4> <p>AWS認定だと以下が該当します。</p> <ul> <li>AWS Certified Solutions Architect - Associate (SAA)</li> <li>AWS Certified Developer - Associate  (DVA)</li> <li>AWS Certified SysOps Administrator - Associate (SOA)</li> </ul> <p>「AWS の知識とスキルを証明し、AWS クラウドのプロフェッショナルとしての信頼性を構築するロールベースの認定」と説明されています。</p> <p>このなかでお勧めするのは、幅広くクラウド環境におけるアーキテクチャを学べる Solutions Architect - Associate (SAA) です。AWS認定試験において最も有名なもので「AWS認定を持っています」と言って相手が想像するのは、たぶんこの認定です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fcertification%2Fcertified-solutions-architect-associate%2F" title="AWS Certified Solutions Architect – Associate 認定" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://aws.amazon.com/jp/certification/certified-solutions-architect-associate/">aws.amazon.com</a></cite></p> <p>残りの Developer は開発、SysOps Administrator はシステム運用を主とした構成となっており、より特化した資格の印象です。これらは関連業務についている場合に取得を検討すると良いでしょう。</p> <h4 id="プロフェッショナル">プロフェッショナル</h4> <p>AWS認定だと以下が該当します。</p> <ul> <li>AWS Certified Solutions Architect - Professional (SAP)</li> <li>AWS Certified DevOps Engineer - Professional (DOP)</li> </ul> <p>「AWS 上で安全かつ最適化された最新のアプリケーションを設計し、プロセスを自動化するために必要な高度なスキルと知識を証明するロールベースの認定」と説明されています。</p> <p>えー、取得しておいてアレですが、、、多くのケースにおいて、ここまでは必要ない気がします。</p> <p>ただしプロフェッショナルというだけあって、かなり幅広い問題がでますし、学んでいると参考になりそうなベストプラクティスが数多く出てきます。これ試してみたい!というシステム構成などなど。</p> <p>自社の新システムをイチから設計するんだ!仕組みから見直してリニューアルするんだ!その際に自分たちが中心となってアーキテクチャを考えるんだ!といった状況になりそうな気配があれば、取得しておくと大きな力になるでしょう。</p> <h4 id="専門知識認定">専門知識認定</h4> <p>こちらはより専門性が高い分野なので、今回は説明しないでおきます。</p> <p>ただ専門性は高いものの、範囲が狭いので、試験の難易度としては SAP より低いと感じました。なので業務に関わる分野、興味のある分野があれば、こちらから選んで先に取得するのもアリかとおもいます。</p> <p>ちなみに私はアプリケーション開発が本業なので、まずデータベースとセキュリティを取得しました。いろいろ参考となり助かっています。</p> <h3 id="お勧めの勉強方法">お勧めの勉強方法</h3> <p>AWS資格認定は3年間しか有効でないので注意が必要です。上位資格を取ると自動延長されるので、例えばSA系だと以下のようなパスで学んでいけば、それぞれの資格を維持できます。</p> <p>Cloud Practitioner (CLF)<br /> ⇒ 3年以内に Solutions Architect - Associate (SAA)<br /> ⇒ 3年以内に Solutions Architect - Professional (SAP)</p> <p>ちなみに DOP は DVA と SOA 両方の上位資格となるので、これら2つ (CLF を含めると3つ) が延長されます。</p> <p>試験対策としては、やはり受験する資格にあった受験本を購入するのが良いかとおもいます。学習する内容がうまく整理されています。それをざっくり読んで、気になったところ、わからないところを調べていく。検索して情報を探したり、公式ドキュメントを読んでみたり、Blackbelt の動画や資料を見てみたりとか。</p> <h3 id="というわけで">というわけで</h3> <p>AWS認定資格をネタに、クラウドエンジニア向け資格を取得しようという内容で、初歩的な内容ですが投稿させていただきました。</p> <p>個人的に、認定資格を取得して得られたメリットは以下になります。</p> <ul> <li>クラウド環境におけるシステム構築の良い知見(ベストプラクティス)を効率よく学べた</li> <li>ベストプラクティスをもとに、これまでの自分の経験・知見を整理できた</li> <li>様々な事例や構築パターンを学んだことで、基本的なシステムイメージがぱっと浮かぶようになった</li> <li>他のエンジニアと会話する際の語彙(共通認識のある技術ワード)が増え、話しやすく、説明しやすくなった</li> </ul> <p>弊社では資格取得に対して補助する制度があります。AWS認定試験も全て対象になっており、受験費用だけでなく、学習本や Udemy 講座の購入費用が補助されます。私としては非常に助かっており、嬉しい制度です。</p> <p>以上、読んでいただきありがとうございました。マネックスの採用にご興味のある方は、ぜひ以下募集をご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.monexgroup.jp%2Frecruit%2F" title="マネックス 採用サイト" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.monexgroup.jp/recruit/">www.monexgroup.jp</a></cite></p> <p> </p> toshio_yamashita ChatGPTのFunction Calling使ってみた hatenablog://entry/820878482951561349 2023-07-24T10:38:45+09:00 2023-07-24T10:38:45+09:00 こんにちは、あるいはこんばんは 新卒3年目の野地です。最近は生成AIの波にのまれています。aiチャット OpenAIのChatGPTがプログラミングの新たなパラダイムを形成しています。 特にGoogle Apps Script(GAS)のような手軽な環境と組み合わせることで非エンジニアでも様々な業務効率化ができ、新たな可能性が広がっています。 今回はその一例として、OpenAIのChat API の機能、functionCallを使用したフリーアドレス管理について試したものを紹介します。 フリーアドレスの管理とChatGPT マネックスでは、システム開発部署の座席配置はフリーアドレス形式を採用… <p>こんにちは、あるいはこんばんは<br/> 新卒3年目の野地です。最近は生成AIの波にのまれています。<figure class="figure-image figure-image-fotolife" title="aiチャット"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/daiki_noji/20230721/20230721135501.jpg" width="640" height="492" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>aiチャット</figcaption></figure></p> <p>OpenAIのChatGPTがプログラミングの新たなパラダイムを形成しています。<br/> 特にGoogle Apps Script(GAS)のような手軽な環境と組み合わせることで非エンジニアでも様々な業務効率化ができ、新たな可能性が広がっています。</p> <p>今回はその一例として、OpenAIのChat API の機能、functionCallを使用したフリーアドレス管理について試したものを紹介します。</p> <h1 id="フリーアドレスの管理とChatGPT">フリーアドレスの管理とChatGPT</h1> <p>マネックスでは、システム開発部署の座席配置はフリーアドレス形式を採用しています。<br/> 具体的には、Googleスプレッドシートに名前を入力し、座席を指定する方式です。<br/> (それは果たして「フリー」アドレスなのか…?)</p> <p>この方法をさらに便利にするために、ChatGPTとGASを利用したチャット形式の座席予約を試してみました。</p> <h1 id="functionCallの使用方法">functionCallの使用方法</h1> <p>functionCallは、ChatGPTが適切な関数を呼び出すことを可能にする新機能です。</p> <p>以下は、ChatGPTからAPI応答を受け取るコードの一例です。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// ChatGPT API関連定数</span> <span class="synStatement">const</span> SECRET_KEY = <span class="synConstant">&quot;***&quot;</span>; <span class="synStatement">const</span> MAX_TOKENS = 2000; <span class="synStatement">const</span> MODEL_NAME = <span class="synConstant">&quot;gpt-3.5-turbo&quot;</span>;  <span class="synStatement">const</span> MODEL_TEMP = 0.5; <span class="synStatement">const</span> URL = <span class="synConstant">&quot;https://api.openai.com/v1/chat/completions&quot;</span>; <span class="synIdentifier">function</span> GPT(<span class="synStatement">prompt</span>) <span class="synIdentifier">{</span> <span class="synStatement">const</span> payload = <span class="synIdentifier">{</span> model: MODEL_NAME, messages: <span class="synStatement">prompt</span>, temperature: MODEL_TEMP, max_tokens: MAX_TOKENS, functions: FUNCTIONS, <span class="synIdentifier">}</span>; <span class="synStatement">const</span> options = <span class="synIdentifier">{</span> contentType: <span class="synConstant">&quot;application/json&quot;</span>, headers: <span class="synIdentifier">{</span> Authorization: <span class="synConstant">&quot;Bearer &quot;</span> + SECRET_KEY <span class="synIdentifier">}</span>, payload: JSON.stringify(payload), muteHttpExceptions : <span class="synConstant">true</span>, <span class="synIdentifier">}</span>; <span class="synStatement">const</span> res = JSON.parse(UrlFetchApp.fetch(URL, options).getContentText()); <span class="synIdentifier">}</span> </pre> <p>このコードでは、Chat APIの呼び出しに使用するfunctionCallパラメータが重要なポイントとなります。<br/> 以下のように、複数の関数の定義と説明を設定します。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// function callingの対象.適宜加える</span> <span class="synStatement">const</span> FUNCTIONS = <span class="synIdentifier">[</span> <span class="synIdentifier">{</span> name: <span class="synConstant">&quot;get_vacant_seats&quot;</span>, description: <span class="synConstant">&quot;Get the current vacant seats&quot;</span>, parameters: <span class="synIdentifier">{</span> type: <span class="synConstant">&quot;object&quot;</span>, properties: <span class="synIdentifier">{}</span>, <span class="synIdentifier">}</span> <span class="synIdentifier">}</span>, <span class="synIdentifier">{</span> name: <span class="synConstant">&quot;reserve_seat&quot;</span>, description: <span class="synConstant">&quot;reserve the specified seat by name&quot;</span>, parameters: <span class="synIdentifier">{</span> type: <span class="synConstant">&quot;object&quot;</span>, properties: <span class="synIdentifier">{</span> seat: <span class="synIdentifier">{</span> type: <span class="synConstant">&quot;string&quot;</span>, description: <span class="synConstant">&quot;the seat which function caller wants to reserve e.g. 'D17' &quot;</span> <span class="synIdentifier">}</span>, name: <span class="synIdentifier">{</span> type: <span class="synConstant">&quot;string&quot;</span>, description: <span class="synConstant">&quot;reservation name&quot;</span> <span class="synIdentifier">}</span>, <span class="synIdentifier">}</span>, required: <span class="synIdentifier">[</span><span class="synConstant">&quot;seat&quot;</span>, <span class="synConstant">&quot;name&quot;</span><span class="synIdentifier">]</span> <span class="synIdentifier">}</span> <span class="synIdentifier">}</span>, <span class="synIdentifier">]</span> </pre> <p>これによりChatGPTはユーザーの入力から適切な関数を判断し、「この関数をこの引数で呼び出してください」と指示してくれます。<br/> このif文の条件で判断しているように、responseのmessageの中にfunction_callという項目があります。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink> <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synStatement">if</span> (!!res.choices<span class="synIdentifier">[</span>0<span class="synIdentifier">]</span> &amp;&amp; !!res.choices<span class="synIdentifier">[</span>0<span class="synIdentifier">]</span>.message.function_call) <span class="synIdentifier">{</span> </pre> <p>呼び出すべき関数がない場合、function_callの項目は無い状態で応答が返されます。</p> <p>ChatGPTに「このFunction呼んでください」と言われたら、素直に実行します。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">let</span> result = <span class="synConstant">''</span>; <span class="synStatement">if</span> (!!res.error) <span class="synIdentifier">{</span> result = res.error.message; <span class="synStatement">return</span> result; <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synStatement">if</span> (!!res.choices<span class="synIdentifier">[</span>0<span class="synIdentifier">]</span> &amp;&amp; !!res.choices<span class="synIdentifier">[</span>0<span class="synIdentifier">]</span>.message.function_call) <span class="synIdentifier">{</span> <span class="synStatement">const</span> function_name = res.choices<span class="synIdentifier">[</span>0<span class="synIdentifier">]</span>.message.function_call.name; <span class="synStatement">const</span> function_args = JSON.parse(res.choices<span class="synIdentifier">[</span>0<span class="synIdentifier">]</span>.message.function_call.<span class="synIdentifier">arguments</span>); <span class="synStatement">const</span> f_res = globalThis<span class="synIdentifier">[</span>FUNCTION_MAP<span class="synIdentifier">[</span>function_name<span class="synIdentifier">]]</span>(function_args); <span class="synStatement">prompt</span>.push(<span class="synIdentifier">{</span>role: <span class="synConstant">&quot;function&quot;</span>, name: function_name, content: f_res<span class="synIdentifier">}</span>); <span class="synComment">// function_callが無くなるまで呼び出し</span> result = GPT(<span class="synStatement">prompt</span>); <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synIdentifier">{</span> result = res.choices<span class="synIdentifier">[</span>0<span class="synIdentifier">]</span>.message.content; <span class="synIdentifier">}</span> </pre> <p>GAS上での関数名とChat APIに渡すfunction名をマッピングして、globalThisで関数名の文字列から実行しています。<br/> (同名にしてしまえばマッピングは不要ですね...)</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// 各function_nameとスクリプト上の関数名定義との紐づけ</span> <span class="synStatement">const</span> FUNCTION_MAP = <span class="synIdentifier">{</span> <span class="synConstant">'get_vacant_seats'</span>: <span class="synConstant">'getSeats'</span>, <span class="synConstant">'reserve_seat'</span>: <span class="synConstant">'reserveSeat'</span>, <span class="synIdentifier">}</span> </pre> <p>対応するgetSeats()など、実際の関数は別で実装してください。今回は割愛します。</p> <p>そして、その結果は再度ChatGPTへの入力にセットされ、次のステップへと進みます。<br/> このステップは、ChatGPTが呼び出すべき関数がないと判断するまで繰り返されます。</p> <p>最終的なコード全体は以下の通りです。 <details> <summary>最終的なコード</summary></p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// ChatGPT API関連定数</span> <span class="synStatement">const</span> SECRET_KEY = <span class="synConstant">&quot;****&quot;</span>; <span class="synStatement">const</span> MAX_TOKENS = 2000; <span class="synStatement">const</span> MODEL_NAME = <span class="synConstant">&quot;gpt-3.5-turbo&quot;</span>; <span class="synStatement">const</span> MODEL_TEMP = 0.5; <span class="synStatement">const</span> URL = <span class="synConstant">&quot;https://api.openai.com/v1/chat/completions&quot;</span>; <span class="synComment">// function callingの対象.適宜加える</span> <span class="synStatement">const</span> FUNCTIONS = <span class="synIdentifier">[</span> <span class="synIdentifier">{</span> name: <span class="synConstant">&quot;get_vacant_seats&quot;</span>, description: <span class="synConstant">&quot;Get the current vacant seats&quot;</span>, parameters: <span class="synIdentifier">{</span> type: <span class="synConstant">&quot;object&quot;</span>, properties: <span class="synIdentifier">{}</span>, <span class="synIdentifier">}</span> <span class="synIdentifier">}</span>, <span class="synIdentifier">{</span> name: <span class="synConstant">&quot;reserve_seat&quot;</span>, description: <span class="synConstant">&quot;reserve the specified seat by name&quot;</span>, parameters: <span class="synIdentifier">{</span> type: <span class="synConstant">&quot;object&quot;</span>, properties: <span class="synIdentifier">{</span> seat: <span class="synIdentifier">{</span> type: <span class="synConstant">&quot;string&quot;</span>, description: <span class="synConstant">&quot;the seat which function caller wants to reserve e.g. 'D17' &quot;</span> <span class="synIdentifier">}</span>, name: <span class="synIdentifier">{</span> type: <span class="synConstant">&quot;string&quot;</span>, description: <span class="synConstant">&quot;reservation name&quot;</span> <span class="synIdentifier">}</span>, <span class="synIdentifier">}</span>, required: <span class="synIdentifier">[</span><span class="synConstant">&quot;seat&quot;</span>, <span class="synConstant">&quot;name&quot;</span><span class="synIdentifier">]</span> <span class="synIdentifier">}</span> <span class="synIdentifier">}</span>, <span class="synIdentifier">]</span>; <span class="synComment">// 各function_nameとスクリプト上の関数名定義との紐づけ</span> <span class="synStatement">const</span> FUNCTION_MAP = <span class="synIdentifier">{</span> <span class="synConstant">'get_vacant_seats'</span>: <span class="synConstant">'getSeats'</span>, <span class="synConstant">'reserve_seat'</span>: <span class="synConstant">'reserveSeat'</span>, <span class="synIdentifier">}</span> <span class="synIdentifier">function</span> GPT(<span class="synStatement">prompt</span>) <span class="synIdentifier">{</span> <span class="synStatement">const</span> payload = <span class="synIdentifier">{</span> model: MODEL_NAME, messages: <span class="synStatement">prompt</span>, temperature: MODEL_TEMP, max_tokens: MAX_TOKENS, functions: FUNCTIONS, <span class="synIdentifier">}</span>; <span class="synStatement">const</span> options = <span class="synIdentifier">{</span> contentType: <span class="synConstant">&quot;application/json&quot;</span>, headers: <span class="synIdentifier">{</span> Authorization: <span class="synConstant">&quot;Bearer &quot;</span> + SECRET_KEY <span class="synIdentifier">}</span>, payload: JSON.stringify(payload), muteHttpExceptions : <span class="synConstant">true</span>, <span class="synIdentifier">}</span>; <span class="synStatement">const</span> res = JSON.parse(UrlFetchApp.fetch(URL, options).getContentText()); <span class="synIdentifier">let</span> result = <span class="synConstant">''</span>; <span class="synStatement">if</span> (!!res.error) <span class="synIdentifier">{</span> result = res.error.message; <span class="synStatement">return</span> result; <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synStatement">if</span> (!!res.choices<span class="synIdentifier">[</span>0<span class="synIdentifier">]</span> &amp;&amp; !!res.choices<span class="synIdentifier">[</span>0<span class="synIdentifier">]</span>.message.function_call) <span class="synIdentifier">{</span> <span class="synStatement">const</span> function_name = res.choices<span class="synIdentifier">[</span>0<span class="synIdentifier">]</span>.message.function_call.name; <span class="synStatement">const</span> function_args = JSON.parse(res.choices<span class="synIdentifier">[</span>0<span class="synIdentifier">]</span>.message.function_call.<span class="synIdentifier">arguments</span>); <span class="synStatement">const</span> f_res = globalThis<span class="synIdentifier">[</span>FUNCTION_MAP<span class="synIdentifier">[</span>function_name<span class="synIdentifier">]]</span>(function_args); <span class="synStatement">prompt</span>.push(<span class="synIdentifier">{</span>role: <span class="synConstant">&quot;function&quot;</span>, name: function_name, content: f_res<span class="synIdentifier">}</span>); <span class="synComment">// function_callが無くなるまで呼び出し</span> result = GPT(<span class="synStatement">prompt</span>); <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synIdentifier">{</span> result = res.choices<span class="synIdentifier">[</span>0<span class="synIdentifier">]</span>.message.content; <span class="synIdentifier">}</span> <span class="synStatement">return</span> result; <span class="synIdentifier">}</span> </pre> <p></details></p> <h1 id="ChatGPTとGASによる業務効率化">ChatGPTとGASによる業務効率化</h1> <p>このコードで、通常は複数ステップを要する座席の予約作業を、単一の指示で完了させることが可能となります。<br/> 「適当な席を○○(名前)で予約して」という単一の指示により、ChatGPTとGASは空席の検索から予約までを自動で行い、「席××が「○○」で予約されました」と返信してくれます。 夢が広がりますね。</p> <h1 id="時代の変わり目">時代の変わり目</h1> <p>当社マネックスでは、お客様向け・社内向けと両面でChatGPT等生成AIの活用を積極的に検討し、実験しています。<br/> 自由で創造的な環境を提供するマネックスで、一緒に新しい可能性を追求してみませんか? 求人情報は以下をご覧ください!!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fmonex%2Fhomes%2F2366" title="募集一覧 / マネックスグループ株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/monex/homes/2366">open.talentio.com</a></cite></p> daiki_noji 【Python】LambdaでAWS ECSサービスのタスク起動数を操作/取得する hatenablog://entry/820878482946825647 2023-07-07T12:46:57+09:00 2023-07-07T12:46:57+09:00 こんにちは、エンジニアの田代です。 バッチ処理等において、ECSサービスのタスク数を調整したいケースがあるのではないでしょうか。 Step Functionsを利用する等複数の方法が考えられますが、今回はAWS外のジョブ管理システムから操作することを想定して、 API Gateway + Lambdaで実装したのでサンプルを共有したいと思います。 ECSサービスのタスク起動数を更新するLambda Lambdaコード import boto3 ecs = boto3.client('ecs') def lambda_handler(event, context): params = event… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/nullstack/20230703/20230703203823.jpg" alt="container" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは、エンジニアの田代です。<br/> バッチ処理等において、ECSサービスのタスク数を調整したいケースがあるのではないでしょうか。<br/> Step Functionsを利用する等複数の方法が考えられますが、今回はAWS外のジョブ管理システムから操作することを想定して、<br/> API Gateway + Lambdaで実装したのでサンプルを共有したいと思います。</p> <h3 id="ECSサービスのタスク起動数を更新するLambda">ECSサービスのタスク起動数を更新するLambda</h3> <h2 id="Lambdaコード">Lambdaコード</h2> <div class="code-title" data-title="update_desired_count.py"> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> boto3 ecs = boto3.client(<span class="synConstant">'ecs'</span>) <span class="synStatement">def</span> <span class="synIdentifier">lambda_handler</span>(event, context): params = event[<span class="synConstant">'queryStringParameters'</span>] <span class="synComment"># クエリパラメータから対象のECSクラスターを取得</span> cluster = params[<span class="synConstant">'cluster'</span>] <span class="synComment"># クエリパラメータから対象のECSサービスを取得</span> service = params[<span class="synConstant">'service'</span>] <span class="synComment"># クエリパラメータから設定する起動数を取得</span> desired_count = <span class="synIdentifier">int</span>(params[<span class="synConstant">'desiredCount'</span>]) response = ecs.update_service( cluster=cluster, service=service, desiredCount=desired_count ) <span class="synIdentifier">print</span>(response) <span class="synStatement">return</span> { <span class="synConstant">'statusCode'</span>: <span class="synConstant">200</span> } </pre> </div> <h2 id="実行ロール用のIAMポリシー">実行ロール用のIAMポリシー</h2> <div class="code-title" data-title="allow-update-service"> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">Version</span>&quot;: &quot;<span class="synConstant">2012-10-17</span>&quot;, &quot;<span class="synStatement">Statement</span>&quot;: <span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">Sid</span>&quot;: &quot;<span class="synConstant">allow-update-service</span>&quot;, &quot;<span class="synStatement">Effect</span>&quot;: &quot;<span class="synConstant">Allow</span>&quot;, &quot;<span class="synStatement">Action</span>&quot;: &quot;<span class="synConstant">ecs:UpdateService</span>&quot;, &quot;<span class="synStatement">Resource</span>&quot;: &quot;<span class="synConstant">*</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span> </pre> </div> <h3 id="ECSサービスのタスク起動数を取得するLambda">ECSサービスのタスク起動数を取得するLambda</h3> <h2 id="Lambdaコード-1">Lambdaコード</h2> <div class="code-title" data-title="get_task_count.py"> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> boto3 <span class="synPreProc">import</span> json ecs = boto3.client(<span class="synConstant">'ecs'</span>) <span class="synStatement">def</span> <span class="synIdentifier">get_healthy_task_count</span>(cluster: <span class="synIdentifier">str</span>, tasks_list: <span class="synIdentifier">list</span>) -&gt; <span class="synIdentifier">int</span>: <span class="synComment"># 各タスク内の全てのコンテナのヘルスチェックステータスがHEALTHYの場合, 起動タスク数を1加算する</span> healthy_task_count = <span class="synConstant">0</span> <span class="synStatement">if</span> <span class="synIdentifier">len</span>(tasks_list) == <span class="synConstant">0</span>: <span class="synStatement">return</span> healthy_task_count describe_tasks_response = ecs.describe_tasks( cluster=cluster, tasks=tasks_list ) <span class="synStatement">for</span> task <span class="synStatement">in</span> describe_tasks_response[<span class="synConstant">'tasks'</span>]: <span class="synStatement">if</span> <span class="synIdentifier">all</span>(i[<span class="synConstant">'healthStatus'</span>] == <span class="synConstant">'HEALTHY'</span> <span class="synStatement">for</span> i <span class="synStatement">in</span> task[<span class="synConstant">'containers'</span>]): healthy_task_count += <span class="synConstant">1</span> <span class="synStatement">return</span> healthy_task_count <span class="synStatement">def</span> <span class="synIdentifier">lambda_handler</span>(event, context): params = event[<span class="synConstant">'queryStringParameters'</span>] <span class="synComment"># クエリパラメータから対象のECSクラスターを取得</span> cluster = params[<span class="synConstant">'cluster'</span>] <span class="synComment"># クエリパラメータから対象のECSサービスを取得</span> service = params[<span class="synConstant">'service'</span>] <span class="synComment"># RUNNINGステータスのタスクを取得</span> list_tasks_response = ecs.list_tasks( cluster=cluster, serviceName=service, desiredStatus=<span class="synConstant">'RUNNING'</span> ) running_tasks = list_tasks_response[<span class="synConstant">'taskArns'</span>] healthy_task_count = get_healthy_task_count(cluster, running_tasks) body = {<span class="synConstant">'taskCount'</span>: healthy_task_count} <span class="synStatement">return</span> { <span class="synConstant">'statusCode'</span>: <span class="synConstant">200</span>, <span class="synConstant">'body'</span>: json.dumps(body), <span class="synConstant">'headers'</span>: { <span class="synConstant">'Content-Type'</span>: <span class="synConstant">'application/json'</span> } } </pre> </div> <h2 id="実行ロール用のIAMポリシー-1">実行ロール用のIAMポリシー</h2> <div class="code-title" data-title="allow-list-tasks"> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">Version</span>&quot;: &quot;<span class="synConstant">2012-10-17</span>&quot;, &quot;<span class="synStatement">Statement</span>&quot;: <span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">Sid</span>&quot;: &quot;<span class="synConstant">allow-list-tasks</span>&quot;, &quot;<span class="synStatement">Effect</span>&quot;: &quot;<span class="synConstant">Allow</span>&quot;, &quot;<span class="synStatement">Action</span>&quot;: <span class="synSpecial">[</span> &quot;<span class="synConstant">ecs:ListTasks</span>&quot;, &quot;<span class="synConstant">ecs:describeTasks</span>&quot; <span class="synSpecial">]</span>, &quot;<span class="synStatement">Resource</span>&quot;: &quot;<span class="synConstant">*</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span> </pre> </div> <h3 id="実行結果">実行結果</h3> <p>各LambdaのトリガーとしてAPI Gatewayを設定して、それぞれ実行してみます。</p> <div class="code"> <pre class="code" data-lang="" data-unlink>$ curl https://xxx.execute-api.ap-northeast-1.amazonaws.com/xxx/update-desired-count?cluster=EcsClusterName&amp;service=EcsServiceName&amp;desiredCount=2</pre> </div> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/nullstack/20230703/20230703201915.png" alt="ecs" width="929" height="302" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>指定した数のタスクが起動しました。<br/> この状態でタスク起動数取得Lambdaを実行します。</p> <div class="code"> <pre class="code" data-lang="" data-unlink>$ curl https://xxx.execute-api.ap-northeast-1.amazonaws.com/xxx/get-task-count?cluster=EcsClusterName&amp;service=EcsServiceName</pre> </div> <div class="code"> <pre class="code" data-lang="" data-unlink>{&#34;taskCount&#34;: 2}</pre> </div> <p>期待通りの結果が返りました。<br/> 今回の記事が皆様の参考になれば幸いです。</p> <div class="entry-writer"> <div class="image-round"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/monex_engineer/20200120/20200120123608.png" /></div> <span class="name">田代 侑大</span><span class="job">システム開発部 マネックス・ラボ</span></div> nullstack Webアプリケーション開発への新たな風 T3 Stack hatenablog://entry/820878482942884838 2023-06-23T09:22:50+09:00 2023-06-23T09:22:50+09:00 こんにちは、システム開発一部の橋本です。 Webアプリケーション開発は日進月歩の進化を遂げており、近年の開発の手法としてT3 Stackが話題を攫っています。今回はそちらについてご紹介していきたいと思います。 T3 StackはTheo氏によって提唱された技術スタックで、その核心にあるのは「simplicity」、「modularity」、「full-stack typesafety」の三つの考え方です。 具体的には、T3 Stackは以下の技術を中心に構成されています。 Next.js TypeScript tRPC Tailwind CSS Prisma NextAuth.js これらの技… <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kahashimoto/20230619/20230619133759.png" width="1200" height="654" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>こんにちは、システム開発一部の橋本です。</p> <p>Webアプリケーション開発は日進月歩の進化を遂げており、近年の開発の手法としてT3 Stackが話題を攫っています。今回はそちらについてご紹介していきたいと思います。</p> <p>T3 StackはTheo氏によって提唱された技術スタックで、その核心にあるのは「<span style="color: rgba(0, 0, 0, 0.82); font-family: -apple-system, BlinkMacSystemFont, 'Hiragino Kaku Gothic ProN', 'Hiragino Sans', Meiryo, sans-serif, 'Segoe UI Emoji'; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">simplicity</span>」、「modularity」、「full-stack typesafety」の三つの考え方です。</p> <p>具体的には、T3 Stackは以下の技術を中心に構成されています。</p> <ul> <li>Next.js</li> <li>TypeScript</li> <li>tRPC</li> <li>Tailwind CSS</li> <li>Prisma</li> <li>NextAuth.js</li> </ul> <p>これらの技術を用いることで、T3 StackはWeb開発の効率と安全性を向上させることを目指しています。</p> <h3 id="T3-Stackの内訳">T3 Stackの内訳</h3> <h4 id="Nextjs">Next.js</h4> <p><span style="color: #343541; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif, 'Helvetica Neue', Arial, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">Reactをベースにしたフロントエンドフレームワークで、Router、<span style="color: #343541; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif, 'Helvetica Neue', Arial, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">SSR(Server Side Rendering)、SSG(Static Site Generation)、ISR(Incremental Static Regeneration)等といった高度なレンダリング戦略を提供しています。</span></span><span style="color: #343541; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif, 'Helvetica Neue', Arial, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">またSPA(Single Page Application)からMPA(Multi Page Application)まで、さまざまな種類のWebアプリケーションを作成することが可能です。</span></p> <p><span style="color: #343541; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif, 'Helvetica Neue', Arial, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">これにより、動的なコンテンツを持つ大規模なWebアプリケーションでも、最適なアプローチや実装を実現することができます。</span></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnextjs.org%2F" title="Next.js by Vercel - The React Framework" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://nextjs.org/">nextjs.org</a></cite></p> <h4 id="TypeScript">TypeScript</h4> <p>静的型付け言語でJavaScriptをより安全に、より効率的に書くことが出来る今日フロントエンド開発において、最も利用されている言語のひとつです。</p> <p>型安全性はWebアプリケーション開発において重要な要素であり、T3 Stackでもその重要性が強調されています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.typescriptlang.org%2F" title="JavaScript With Syntax For Types." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.typescriptlang.org/">www.typescriptlang.org</a></cite></p> <h4 id="tRPC">tRPC</h4> <p>RPC (Remote Procedure Call)の理念をTypeScriptの静的型付けと組み合わせたライブラリです。</p> <p>これにより、クライアントとサーバー間で型を直接共有することができ、APIのエンドポイントを呼び出すための特殊なクライアントコードを生成します。このため、バックエンドとフロントエンド間でのAPIのドキュメントによる意思疎通の問題を解消し、全体としての開発効率を大幅に向上させます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftrpc.io%2F" title="tRPC - Move Fast and Break Nothing. End-to-end typesafe APIs made easy. | tRPC" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://trpc.io/">trpc.io</a></cite></p> <h4 id="Tailwind-CSS">Tailwind CSS</h4> <p>ユーティリティファーストのCSSフレームワークで、コンポーネントを効率的にコーディングするためのCSSクラスを提供します。</p> <p>これにより、デザイナーや開発者はHTMLの中に直接スタイルを書くことで、視覚的な変更を即座に反映させることができます。また、Tailwind CSSは完全にカスタマイズ可能で、プロジェクトの要件に応じてスタイルを調整することができます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftailwindcss.com%2F" title="Tailwind CSS - Rapidly build modern websites without ever leaving your HTML." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tailwindcss.com/">tailwindcss.com</a></cite></p> <p> </p> <h4 id="Prisma">Prisma</h4> <p>Node.jsとTypeScriptを使用した型安全なORMです。</p> <p>データベースのスキーマ定義を行うことで、モデルベースの型と関数の自動補完を提供します。これにより、アプリケーションのモデルとデータベースのモデルが一致し、より安全にコードを書くことが可能になります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.prisma.io%2F" title="Prisma | Next-generation ORM for Node.js &amp; TypeScript" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.prisma.io/">www.prisma.io</a></cite></p> <h4 id="NextAuthjs">NextAuth.js</h4> <p>Next.jsアプリケーションのためのサーバーサイド認証ライブラリで、セッション管理、認証プロバイダーの統合、CSRF保護など、認証に関連する機能を網羅しています。</p> <p>NextAuth.jsはJWT(JSON Web Tokens)ベースのセッション管理をサポートしており、様々な認証プロバイダーと統合することができます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnext-auth.js.org%2F" title="NextAuth.js" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://next-auth.js.org/">next-auth.js.org</a></cite></p> <p>これらの技術の組み合わせにより、T3 StackはWebアプリケーション開発をシンプルに、かつモジュラーで、そしてエンドツーエンドの型安全を確保したものにします。</p> <p>これにより、開発者はアプリケーションの品質を確保しながら、迅速な開発サイクルを維持することが可能になります。</p> <h3 id="T3-Stackの目指す方向性">T3 Stackの目指す方向性</h3> <p>T3 Stackは、Web開発における特定の問題を解決するために設計されています。<br />しかし、それは単に全ての機能を詰め込むためではなく、スタックに含まれる各技術が解決すべき特定の問題を明確にしていることが重要です。</p> <p>また、T3 Stackは「リスクを積極的に取る」という考え方も提唱しています。<br />最新の技術はリスクを伴いますが、リスクの少ない部分で積極的に新たな技術を導入し、最適なバランスを追求します。</p> <p>そして、T3 Stackにおいて最も重視されるのが「型安全性」です。<br />T3 Stackの中心技術の2つ、TypeScriptとtRPCは、型安全性を強く意識して設計されています。<br />これにより、T3 Stackは型安全なWebアプリケーション開発を実現します。</p> <h3 id="まとめ">まとめ</h3> <p>T3 Stackは、型安全性を重視し、簡潔さとモジュール性をバランス良く組み合わせた新たなWeb開発のアプローチを提供しています。</p> <p>それぞれの技術はそれ自体で優れた機能を持つ一方で、合わせて使用することで効果を最大化することができます。</p> <p>このT3 Stackを体験してみたいという方は、<code>create-t3-app</code>を使うと簡単に始めることができますので、ぜひ試してみてはいかがでしょうか。現代のWeb開発において、T3 Stackは新たな可能性を広げることでしょう。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcreate.t3.gg%2F" title="Create T3 App" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <p><cite class="hatena-citation"><a href="https://create.t3.gg/">create.t3.gg</a></cite></p> <p> </p> kahashimoto Pythonを使ってChatGPTを活用してみる hatenablog://entry/820878482942089060 2023-06-16T19:29:01+09:00 2023-06-19T09:25:42+09:00 こんにちは。インフラグループDBチームのKです。 最近何かと話題のChatGPTですが、以前AIのビジネスに関わっていたことがある私にはディープラーニング以来の驚きです。 ディープラーニングは深層学習と呼ばれるもので、自動で特徴量を抽出し学習していく技術であり、画像認識等様々な分野で大きな効果を出していました。 ChatGPTは自然言語技術を用いたAIになり、まるで人間のように振る舞い、対話や文章を生成することができます。 ベースは大規模言語モデル(LLM:Large Language Models)と呼ばれる大量のテキストデータを使ってトレーニングされた自然言語処理のモデルになります。大量の… <p>こんにちは。インフラグループDBチームのKです。</p> <p> 最近何かと話題のChatGPTですが、以前AIのビジネスに関わっていたことがある私にはディープラーニング以来の驚きです。<br> ディープラーニングは深層学習と呼ばれるもので、自動で特徴量を抽出し学習していく技術であり、画像認識等様々な分野で大きな効果を出していました。</p> <p> ChatGPTは自然言語技術を用いたAIになり、まるで人間のように振る舞い、対話や文章を生成することができます。<br> ベースは大規模言語モデル(LLM:Large Language Models)と呼ばれる大量のテキストデータを使ってトレーニングされた自然言語処理のモデルになります。大量のデータを学習させ、ファインチューニングすることで実現しているようです。</p> <p>詳細は以下のリンクをご参照ください。<br/> <a href="https://openai.com/blog/chatgpt">https://openai.com/blog/chatgpt</a></p> <p>現在、様々な企業にてChatGPT活用への取り組みが行われてますが、今回は開発という視点からPythonを使ってChatGPTを簡単に試してみたいと思います。<br> PythonでChatGPTを活用するには、ChatGPTが提供しているAPIを使用します。APIは有償となり従量課金モデルになります。料金体系はトークンという単位で課金され、APIへリクエスト時およびレスポンス時のデータサイズに依存します。日本語は英語と違ってマルチバイトのため、使用トークンが増える傾向にあるので注意が必要です。</p> <p>以下、PythonでChatGPTを使用するまでの流れになります。</p> <ol> <li>APIキーの発行</li> <li>クレジット上限金額の設定</li> <li>PythonからAPI実行</li> </ol> <h4 id="1-APIキーの発行">1. APIキーの発行</h4> <p>では早速、APIキーの発行を行ってみます。APIキーを発行するには、事前にOpenAIのアカウント登録を済ませておく必要があります。<br> 以下のURLより登録可能です。Googleアカウントの使用も可能です。<br> <a href="https://openai.com/product">https://openai.com/product</a></p> <p>ログインが完了したら、右上のアカウントアイコンから「View API keys」を選択します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eb_infra01/20230616/20230616144457.jpg" width="1200" height="451" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>「API keys」のページに遷移するので、「+ Create new secret key」ボタンをクリックします。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eb_infra01/20230616/20230616144522.jpg" width="1200" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>赤枠のアイコンをクリックし、APIキーを生成します。<br> 発行されたAPIキーを使い、APIリクエストすれば、OpenAIのサービスがAPI経由で利用できます。APIキーは一度しか表示されないため、コピーして大切に保存しておきます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eb_infra01/20230616/20230616160501.jpg" width="1200" height="442" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="2-クレジット上限金額の設定">2. クレジット上限金額の設定</h4> <p>次にクレジットカード支払いの設定を行った上で上限金額の設定を行う必要があります。無償トライアルがあるのでトライアル期間を過ぎてしまった場合は設定が必要になります。料金設定ページから「Set up paid account」をクリックします。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eb_infra01/20230616/20230616160808.jpg" width="1200" height="426" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>個人使用の場合「I'm an individual」、企業での使用の場合「I'm working on behalf of a company」をクリックし、クレジットカード情報や住所などを入力して登録します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eb_infra01/20230616/20230616160623.jpg" width="1200" height="424" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>次に上限金額の設定をします。左タブの「Billing」をクリックし「Usage limits」をクリックします。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eb_infra01/20230616/20230616160924.jpg" width="1200" height="393" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>赤丸で囲った部分に上限金額を入力し、「Save」をクリックします。<br> ハードリミットとは毎月の使用量の上限値となり、ソフトリミットとは通知メールが送信される毎月の使用量のしきい値になります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eb_infra01/20230616/20230616161040.jpg" width="1200" height="559" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="3-PythonからAPI実行">3. PythonからAPI実行</h4> <p>Pythonを実行するにあたり、事前に pip でopenaiライブラリをインストールしておきます。今回私が実行するPython環境は jupyter notebookになります。以下のようなコードを実行してみました。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> openai openai.api_key = <span class="synConstant">&quot;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&quot;</span> res = openai.ChatCompletion.create( model=<span class="synConstant">&quot;gpt-3.5-turbo&quot;</span>, messages=[ {<span class="synConstant">&quot;role&quot;</span>: <span class="synConstant">&quot;user&quot;</span>, <span class="synConstant">&quot;content&quot;</span>: <span class="synConstant">&quot;2022年のカタールワールドカップの優勝チームは?&quot;</span>} ] ) <span class="synIdentifier">print</span>(res.choices[<span class="synConstant">0</span>][<span class="synConstant">&quot;message&quot;</span>][<span class="synConstant">&quot;content&quot;</span>].strip()) </pre> <p>構文の説明ですが、</p> <ul> <li>api_keyの部分には先ほど取得したAPIキーを記載します。直書きはよくないので、環境変数を使用するなどしてラップすると良いです。</li> <li>ChatCompletionのクラスメソッド「create」を使い、modelとmessage引数で内容を渡します。ともに必須の引数となり、</li> <li>modelには自分の使いたいモデル(gpt-3.5-turbo、gpt-4、gpt-4-32k)を指定します。</li> <li>messagesには、"role": "user"、"content"の値に、質問内容を入力します。ここでは激闘だった2022年のカタールワールドカップの優勝チームを聞いてみます。</li> </ul> <p>実行すると以下のような回答が返ってきました。</p> <pre class="code" data-lang="" data-unlink>私は人工知能のモデルであり、将来の結果を正確に予測することはできません。したがって、2022年のカタールワールドカップ優勝チームを予測することはできません。</pre> <p>あまりにも自然な文章に驚きしかありませんが、期待した回答ではありませんでした。色々調べてみるとどうやら2021年までの情報しか持ち合わせていないようです。活用にあたり、留意が必要そうです。</p> <p>では気を取り直して、2018年のロシアワールドカップの優勝チームを聞いてみます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> openai openai.api_key = <span class="synConstant">&quot;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&quot;</span> res = openai.ChatCompletion.create( model=<span class="synConstant">&quot;gpt-3.5-turbo&quot;</span>, messages=[ {<span class="synConstant">&quot;role&quot;</span>: <span class="synConstant">&quot;user&quot;</span>, <span class="synConstant">&quot;content&quot;</span>: <span class="synConstant">&quot;2018年のロシアワールドカップの優勝チームは?&quot;</span>} ] ) <span class="synIdentifier">print</span>(res.choices[<span class="synConstant">0</span>][<span class="synConstant">&quot;message&quot;</span>][<span class="synConstant">&quot;content&quot;</span>].strip()) </pre> <pre class="code" data-lang="" data-unlink>フランスです</pre> <p>無事、正解を聞けました。</p> <p>今回は簡単な使い方を試してみましたが、もっと色々応用ができそうなので今後も試してみたいと思います。</p> eb_infra01 Swift Macrosの始め方 hatenablog://entry/820878482941280398 2023-06-14T00:00:00+09:00 2023-06-14T10:18:29+09:00 こんにちは。マネックス・ラボの佐藤です。プログラミング言語の中では表現力の高いSwiftが好きなので、今回はWWDC2023ネタで書きたいと思います。 Swift5.9から、if elseブロックやswitchが値を返せるようになったりと、細かいところで記述の幅が広がっていて、ますますSwiftが使いやすくなっています。 その中でも一番気になったのが、Swift Macrosです。今回はまだベータ版であるXcode15をダウンロードしつつ、マクロの作り方を理解するためのヒントをまとめたいと思います。 この記事では最後に抽象構文木(AST)を見ながらswift-macro-examplesリポジ… <p>こんにちは。マネックス・ラボの佐藤です。プログラミング言語の中では表現力の高いSwiftが好きなので、今回はWWDC2023ネタで書きたいと思います。</p> <p>Swift5.9から、<code>if else</code>ブロックや<code>switch</code>が値を返せるようになったりと、細かいところで記述の幅が広がっていて、ますますSwiftが使いやすくなっています。 その中でも一番気になったのが、<strong>Swift Macros</strong>です。今回はまだベータ版であるXcode15をダウンロードしつつ、マクロの作り方を理解するためのヒントをまとめたいと思います。</p> <p>この記事では最後に抽象構文木(AST)を見ながらswift-macro-examplesリポジトリの実装を読むことをゴールとし、最初にそれに必要な知識を簡潔に説明したいと思います。</p> <h3 id="CCのマクロとSwift-Macros">C/C++のマクロとSwift Macros</h3> <p>特にC/C++を書いたことがあるプログラマーにとっては、マクロは可読性を下げたり、バグを追いづらくしたりと悪名高い存在ですが、Swiftではモダンな言語らしい実装になっていて、うまく活用すれば重複コードの排除やバグの削減に役立ちそうな作りになっています。</p> <p>Swift Macrosの特徴は、C/C++に見られるようなただの文字列の連結とその置き換えではなく、ASTを元にしたコード生成ができることです。 Swiftの個々のマクロは、宣言とその実装で作られます。実装はSwift言語で記述することができ、その実装の中で、ASTを元にしてコードの構造を解釈しつつ、文字列の形でコードを生成します。</p> <h3 id="2種類のマクロFreestanding-MacrosとAttached-Macros">2種類のマクロ(Freestanding MacrosとAttached Macros)</h3> <p>Swift Macrosには<a href="https://www.swiftlangjp.com/language-guide/macros.html#%E8%87%AA%E7%AB%8B%E5%9E%8B%E3%83%9E%E3%82%AF%E3%83%AD">Freestanding Macros</a>と <a href="https://www.swiftlangjp.com/language-guide/macros.html#%E4%BB%98%E5%B1%9E%E5%9E%8B%E3%83%9E%E3%82%AF%E3%83%AD">Attached Macros</a>の2種類があります。 (このブログは日本語の記事であり読者は日本語の方が慣れていると思われるため、リンク先は日本語訳へのリンクを貼っています。一方でコードを読むときには英語の呼び方で覚えておいた方が理解しやすいと思うので、記事上の表記は英語のままにしています)</p> <p>Freestanding Macrosの方は、それ単体で機能するマクロで、マクロが取った引数を元にコードを生成したい場合にFreestandingとして作成します。</p> <p>もう一つのAttached Macrosは例えば構造体定義の前やメンバーの前に記述し、マクロが付与された構文を解析しつつ、コードを自動生成します。例えば構造体の前につけるマクロを実装すれば、構造体のメンバーを列挙して、そのメンバーごとに追加のコードを生成するようなことができます。 Attached Macrosについてはメンバーを生成するMember macrosや、プロトコルに準拠するComformance macrosなど複数の属性があるのですが、詳しくは後述します。</p> <h3 id="マクロ宣言">マクロ宣言</h3> <p>Swiftでは例えば構造体を書くときに、宣言とその実装をまとめて書くことができます。一方でマクロについては、宣言と、その実装に分けます。 宣言の書き方もThe Swift Programming Languageに<a href="https://www.swiftlangjp.com/language-guide/macros.html#%E3%83%9E%E3%82%AF%E3%83%AD%E5%AE%A3%E8%A8%80">わかりやすく記載</a>があります。</p> <p>リンク先の解説を見ると、Attached Macroの場合には、マクロ宣言の前に複数の@attached()を記述し、マクロが何をするのかを明示します。 例えば<code>@attached(member, names: named(CodingKeys))</code>と記述すると、このマクロは構造体などに対して、<code>CodingKeys</code>というメンバーを生成することを示すことができます。</p> <h3 id="Xcode15でマクロのパッケージを作成してみる">Xcode15でマクロのパッケージを作成してみる</h3> <p>おそらくここからは、実際に手を動かしながら試してみた方が理解しやすいと思うので、Xcode15(beta)をダウンロードして試してみることをお勧めします。 いつも通り、Appleの<a href="https://developer.apple.com/download/all/?q=Xcode">More Downloads</a>でXcodeキーワードで検索すれば見つけることができるはずです。 ダウンロードして解凍し、起動した後、File -> New -> Package...を開くとSwift Macroを選んでパッケージを作成することができます。</p> <center> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/fullstuck_sato/20230613/20230613151209.png" width="1200" height="868" loading="lazy" title="" class="hatena-fotolife" style="width:640px" itemprop="image"></span> </center> <p>今回は名前にこだわる必要も無いので、とりあえずデフォルトのMyMacroでパッケージを作成しました。 <code>Package.swift</code>を眺めながら確認するとわかりやすいと思いますが、ざっくりと4つのファイルが自動生成されます。</p> <center> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/fullstuck_sato/20230613/20230613151521.png" width="518" height="688" loading="lazy" title="" class="hatena-fotolife" style="width:320px" itemprop="image"></span> </center> <ul> <li><code>MyMacro.swift</code> マクロの宣言を記述します。デフォルトでサンプルとして<code>stringify</code>というマクロ宣言が記述されています。</li> <li><code>MyMacroMacro.swift</code> ファイル名がとてもイマイチなのはさておき、マクロの実装を記述します。同じくサンプルである<code>StringifyMacro</code>の実装があります。</li> <li><code>main.swift</code> ここで宣言と実装を行なったマクロの動作確認をすることができます。</li> <li><code>MyMacroTests.swift</code> 実装したマクロのテストを記述することができます。</li> </ul> <p>実際にコーディングしながらマクロの書き方を調べるときには、上記の自動生成されたファイルに追記しながら作業すると効率が良いと思います。</p> <h3 id="サンプルを読み解く">サンプルを読み解く</h3> <p>ありがたいことに、Swift Macrosを提案し、実装されているDouglas_Gregorさんが<a href="https://github.com/DougGregor/swift-macro-examples">swift-macro-examples</a>というリポジトリを用意してくれています。 このサンプルもXcode15で自動生成したプロジェクトと似たような構造になっていて、構造を理解してから読み解くのがお勧めです。</p> <ul> <li><a href="https://github.com/DougGregor/swift-macro-examples/blob/main/MacroExamplesLib/Macros.swift">MacroExamplesLib/Macros.swift</a> サンプルマクロの宣言が列挙されています。</li> <li><a href="https://github.com/DougGregor/swift-macro-examples/tree/main/MacroExamplesPlugin">MacroExamplesPlugin/ディレクトリ</a> サンプルマクロの実装が書かれています。いきなり実装を読んでもよくわからないと思うので、最初はあまり読まなくていいと思います。</li> <li><a href="https://github.com/DougGregor/swift-macro-examples/blob/main/MacroExamples/main.swift">MacroExamples/main.swift</a> サンプルマクロの使用例が書かれています。おそらく、このファイルから読んでいくのがお勧めです。これを見ると、サンプルのマクロをどういうときに使って、何ができるのかだいたいのイメージを掴むことができると思います。</li> </ul> <p>まずは<code>main.swift</code>を眺めていくのが良いのですが、リポジトリをcloneするかダウンロードしてきて、Xcode15で開いてみると良いです。<strong>マクロの上で右クリックをしてExpand Macroを選ぶと、マクロが生成したコードを見ることができます。</strong></p> <p>なお、このリポジトリを最初に開いたときは、ビルドエラー(<code>External macro implementation type 'MacroExamplesPlugin.DictionaryStorageMacro' could not be found for macro '</code>)が出るかもしれません。 その場合には、Xcode上でスキーマをMacroExamplePlugin, MacroExampleLibの順に切り替えながらそれぞれをビルドしてあげると解消できると思います。</p> <p><code>main.swift</code>をExpand Macroを使いながら見てみると、Swift Macrosでどんなことができるのか、イメージが湧いてくると思います。 私の場合には<code>@CustomCodable</code>と<code>@CodableKey</code>がとても気になったので、これらの実装を読み解きながら、自分で実装するときにどんなコードを書けば良いのかを見ていきたいと思います。</p> <p>下記がswift-macro-examplesリポジトリからのコードの抜粋です。</p> <pre class="code lang-swift" data-lang="swift" data-unlink><span class="synType">@CustomCodable</span> <span class="synPreProc">struct</span> <span class="synIdentifier">CustomCodableString</span><span class="synSpecial">:</span> <span class="synType">Codable</span> { <span class="synType">@CodableKey</span><span class="synSpecial">(</span><span class="synType">name: &quot;OtherName&quot;</span><span class="synSpecial">)</span> <span class="synPreProc">var</span> <span class="synIdentifier">propertyWithOtherName</span><span class="synSpecial">:</span> <span class="synType">String</span> <span class="synPreProc">var</span> <span class="synIdentifier">propertyWithSameName</span><span class="synSpecial">:</span> <span class="synType">Bool</span> <span class="synPreProc">func</span> <span class="synIdentifier">randomFunction</span>() { } } </pre> <p>マクロを使わずに自分で<code>CodingKeys</code>を書くとしたら、下記のようになるはずです。構造体が持つプロパティの数だけcaseを書かないといけないので、プロパティが増えると無駄な記述が増えがちです。マクロがこれを生成してくれると記述量も減りますし、どのプロパティがKeyを置き換えたいのかが分かりやすくなるはずです。</p> <pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">struct</span> <span class="synIdentifier">CustomeCodableString</span><span class="synSpecial">:</span> <span class="synType">Codable</span> { <span class="synPreProc">var</span> <span class="synIdentifier">propertyWithOtherName</span><span class="synSpecial">:</span> <span class="synType">String</span> <span class="synPreProc">var</span> <span class="synIdentifier">propertyWithSameName</span><span class="synSpecial">:</span> <span class="synType">Bool</span> <span class="synPreProc">enum</span> <span class="synIdentifier">CodingKeys</span><span class="synSpecial">:</span> <span class="synType">String</span>, CodingKey { <span class="synStatement">case</span> propertyWithOtherName <span class="synIdentifier">=</span> <span class="synConstant">&quot;OtherName&quot;</span> <span class="synStatement">case</span> propertyWithSameName } <span class="synPreProc">func</span> <span class="synIdentifier">randomFunction</span>() { } } </pre> <p>ということで、<code>@CustomCodable</code>と<code>@CodableKey</code>がどのようにしてプロパティを読み取り、自動的に<code>CodingKeys</code>を生成しているのかを読み解いていきたいと思います。</p> <h4 id="CodableKeyマクロ">CodableKeyマクロ</h4> <p>実装は、<a href="https://github.com/DougGregor/swift-macro-examples/blob/main/MacroExamplesPlugin/CodableKey.swift">swift-macro-examples/MacroExamplesPlugin/CodableKey.swift</a>にあります。 中身を見てみるととてもシンプルで、コメントにもある通り、何もしないマクロになっています。これは外側の<code>@CustomCodable</code>側で<code>@CodableKey</code>の存在を確認し、<code>@CodableKey</code>の代わりにコードを生成する実装になっているからです。</p> <p>ちなみに、マクロは外側から展開されていくので、実際には<code>@CustomCodable</code>によるコード生成が行われるだけで、<code>@CodableKey</code>の実装はダミーだといえます。</p> <h4 id="CustomCodableマクロ">CustomCodableマクロ</h4> <p><a href="https://github.com/DougGregor/swift-macro-examples/blob/main/MacroExamplesPlugin/CustomCodable.swift">swift-macro-examples/MacroExamplesPlugin/CustomCodable.swift</a>に実装があります。 こちらはそれなりに中身のあるコードになっています。<code>main.swift</code>にて生成されるコードの検討がついていることが多いので、<a href="https://github.com/DougGregor/swift-macro-examples/blob/f61ac7cdca8dc3557e53f86e7e03df1353908d3e/MacroExamplesPlugin/CustomCodable.swift#L29-L31">returnしている行</a>から逆に追っていくのが読みやすいと思います。 returnで使っている変数を逆に追っていけば、概ねマクロの実装がわかるはずです。</p> <h5 id="キャストしながらの長いプロパティ参照がよくわからない問題">キャストしながらの長いプロパティ参照がよくわからない問題</h5> <p>メタプログラミングではよく見かける、メンバーの属性をたどって、そこからさらに識別子を辿って・・・ということがSwift Macrosにも当てはまります。 <a href="https://github.com/DougGregor/swift-macro-examples/blob/f61ac7cdca8dc3557e53f86e7e03df1353908d3e/MacroExamplesPlugin/CustomCodable.swift#L17">プロパティの抽出を行っている行</a>や <a href="https://github.com/DougGregor/swift-macro-examples/blob/f61ac7cdca8dc3557e53f86e7e03df1353908d3e/MacroExamplesPlugin/CustomCodable.swift#L22">CodableKeyマクロの有無を調べている行</a> がそれです。</p> <p>Swift Macrosを支えているSwift Syntaxに熟知しているならまだしも、いきなりこのコードを書くのは至難の技です。要は想定しているコードを表すASTの構造がわかればよく、 <a href="https://swift-ast-explorer.com/">Swift Ast Explorer.com</a>を使うと、ブラウザ上で簡単に任意のコードのASTを知ることができます。 <code>main.swift</code>で<code>@CustomCodable</code>を使っているコードを投入した結果が以下のキャプチャのようになります。</p> <center> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/fullstuck_sato/20230613/20230613151637.png" width="1200" height="947" loading="lazy" title="" class="hatena-fotolife" style="width:640px" itemprop="image"></span> </center> <p>かなり長いので、今回は使わない<code>randomFunction()</code>を削除し、構造体定義を表すASTの部分まで少しスクロールした状態でキャプチャをとっています。</p> <p>では、<a href="https://github.com/DougGregor/swift-macro-examples/blob/f61ac7cdca8dc3557e53f86e7e03df1353908d3e/MacroExamplesPlugin/CustomCodable.swift#L17">プロパティの抽出を行っている行</a>について、 ASTのどこに対応しているのかを見てみます。といっても、文章で書くよりもコードとASTを並べて図を書いた方がわかりやすいので貼り付けておきます。</p> <center> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/fullstuck_sato/20230613/20230613151718.png" width="1200" height="852" loading="lazy" title="" class="hatena-fotolife" style="width:640px" itemprop="image"></span> </center> <p>ざっと説明すると、<code>member</code>がASTのMemberDeclListにぶら下がっている個々のMemberDeclListItemを指していて、<code>as(VariableDeclSyntax.self)?</code>のダウンキャストを使ってVariableDeclである<code>member</code>をフィルタしています。 さらに<code>bindings</code>プロパティがASTでいうところのPatternBindingListに該当するので、このリストの先頭が<code>IdentifierPatternSyntax</code>であるかをダウンキャストすることで判定しています。 <code>IdentifierPatternSyntax</code>であれば、<code>.identifier.text</code>プロパティが構造体のプロパティ名を表すので、これを<code>propertyName</code>に代入しています。</p> <p><a href="https://github.com/DougGregor/swift-macro-examples/blob/f61ac7cdca8dc3557e53f86e7e03df1353908d3e/MacroExamplesPlugin/CustomCodable.swift#L22">CodableKeyマクロの有無を調べている行</a>の各プロパティがAST上のどこに該当するかはこちら。</p> <center> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/fullstuck_sato/20230613/20230613151749.png" width="1200" height="827" loading="lazy" title="" class="hatena-fotolife" style="width:640px" itemprop="image"></span> </center> <p>こちらについてはもはや説明はいらないと思うので、割愛します。</p> <p>サンプルマクロの実装コードとASTの対比を読み解くコツは、まずマクロ実装のコード上でプロパティごとに改行を入れることと、Swift Ast Explorerでマウスカーソルを重ねると元のコードのどこに対応してくれるのかを示してくれるので、これを参考にすると良いと思います。</p> <h3 id="自分でマクロを作るには">自分でマクロを作るには</h3> <p>今回は主に<code>@CustomCodable</code>を読み解きましたが、swift-macro-examplesリポジトリには他にもたくさんのサンプル実装があります。 自分が作りたいマクロの性質になるべく近いサンプルを探し、そのコードとASTを見比べながら、自分のマクロに取り込んでいくのが良いと思います。</p> <p>また、Swift Macrosを支えているSwiftSyntaxのリポジトリを覗いてみるのも良いと思います。特に<a href="https://github.com/apple/swift-syntax/tree/main/Sources/SwiftSyntaxMacros/MacroProtocols">swift-syntax/Sources/SwiftSyntaxMacros/MacroProtocols/</a> ディレクトリに、マクロ実装をするときに指定できるプロトコルが列挙されています。気になるプロトコルを見つけたら、Xcode上でそのプロパティに準拠させた構造体を書くと、おなじみの"Type 'MyMacroSample' does not conform to protocol 'ConformanceMacro'"といったエラーとともに自動で必要なコードを補完してくれるので、これを元にして調査したり、実装してみても良いと思います。</p> <h3 id="おわりに">おわりに</h3> <p>マクロはコードの自動生成に他ならず、コードの可読性を低下させる場合もあります。一方で、今回見てきたサンプルのCodableKeyマクロは、大きな構造体の一部のプロパティだけのために長いCodingKeysを書かなければならないような場合に、とても簡潔な記述をもたらしてくれます。swift-macro-examplesリポジトリには他にも有用そうなサンプルが多々あり、マクロによって少ないコード量で安全なコードを書けるようになる可能性を秘めています。ただし、チームで独自にマクロを作る場合には、最初にマクロの使い方を書いてみて、チーム内の合意をとってから実装をするのが良いと思います。</p> <h3 id="参考にしたドキュメント">参考にしたドキュメント</h3> <h4 id="The-Swift-Programming-Languageのドキュメント">The Swift Programming Languageのドキュメント</h4> <p>すでにSwift5.9(beta)のドキュメントが公開されており、入門用にちょうどいい程度の詳しさでまとまっているので、まずはこちらを読むのが良いと思います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.swift.org%2Fswift-book%2Fdocumentation%2Fthe-swift-programming-language%2Fmacros" title="Documentation" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros">docs.swift.org</a></cite></p> <p>ありがたいことに、すでに日本語訳も出ています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.swiftlangjp.com%2Flanguage-guide%2Fmacros.html" title="マクロ(Macros) · The Swift Programming Language日本語版" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.swiftlangjp.com/language-guide/macros.html">www.swiftlangjp.com</a></cite></p> <p>ボリュームがそれほどあるわけではないので、一読するのがおすすめです。</p> <h4 id="WWDC2023のセッション">WWDC2023のセッション</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdeveloper.apple.com%2Fvideos%2Fplay%2Fwwdc2023%2F10167%2F" title="Expand on Swift macros - WWDC23 - Videos - Apple Developer" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://developer.apple.com/videos/play/wwdc2023/10167/">developer.apple.com</a></cite> マクロが想定している位置に書かれているかどうかを検査するコードも含まれています。使い勝手のいいマクロを書くためには必要なテクニックであろうと思います。</p> <h4 id="プロポーザル">プロポーザル</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fapple%2Fswift-evolution%2Fblob%2Fmain%2Fproposals%2F0382-expression-macros.md" title="swift-evolution/proposals/0382-expression-macros.md at main · apple/swift-evolution" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/apple/swift-evolution/blob/main/proposals/0382-expression-macros.md">github.com</a></cite> 提案時点からの情報が詰め込まれているので、最終的な仕様を知るには遠回りになってしまいますが、言語に新機能が追加されるまでのプロセスを垣間見ることができるので面白いです。</p> <div class="entry-writer"> <div class="image-round"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/monex_engineer/20191206/20191206155435.jpg" /></div> <p><span class="name">佐藤 俊介</span><span class="job">マネックス・ラボ</span></p> </div> fullstuck_sato 投資がはじめてな会社員にとって、投資の入り口としては配当金投資が合ってるのではないだろうか……? hatenablog://entry/820878482935780863 2023-06-02T00:00:00+09:00 2023-07-10T16:46:44+09:00 投資がはじめてな会社員にとっては配当金投資が良いのではないだろうかという一意見を記事にしたものです。また、記事の最後に内製開発グループメンバーの投資経験アンケート結果も載せています。 <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/rtakigawa/20230710/20230710163451.jpg" width="640" height="427" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><br /><br />システム開発一部、内製開発グループのラッコです。</p> <p>最近iPhoneをたたき割ってしまいました……。</p> <p>個人で主に米国株の現物投資をしてはいますが、日々「投資、なんもわからん……」ってなっています。</p> <p>今回は<strong>マネックス</strong>エンジニアブログということで、技術寄りの話は他メンバーに任せ、証券っぽいお話をさせていただきます。(証券というよりは投資ですが)</p> <h3 id="配当金っていいよね">配当金って……いいよね……</h3> <p>いきなりですが!</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/rtakigawa/20230526/20230526112231.png" width="613" height="153" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/rtakigawa/20230526/20230526112314.png" width="611" height="157" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>↑の画像は、最近(?)私が受け取った米国株の配当金受領メールです。<br />銘柄毎にバラバラで来ますし、米国株の配当なので基本的には四半期毎ですが、ものによっては毎月届きます。</p> <p>この画像そのままだと分かり辛いので、少しかみ砕いてみましょう。<br />高い方であれば251.31ドル。現在のレートで約3.5万円ですね。<br />似たような値だとIT会社の資格取得でもらえる賞金ぐらいの金額でしょうか。これが資格勉強せずとも四半期ごとに入ってくるというのはなかなかではないでしょうか?</p> <p>低い方の73.34ドルは現在のレートだと約1万円。<br />先程よりは少ないですが、それでも飲み会2~3回分ぐらいにはなりますね。</p> <p>上記銘柄は私のポートフォリオの中でも比較的配当金が多い方で、当然、銘柄によっては10ドル程度のものもあります。<br />それでもランチ代ぐらいにはなっているので個人的には十分という認識です。</p> <p>どうでしょう?配当金いいなぁってなりませんか?</p> <p>※(余談)マネックスで米国株を持っていて配当金がでると、このような形でメールでお知らせしてくれます。私はこのメールを受け取るのを密かな楽しみにしたりしていたり</p> <h4 id="投資って何から手を付ければいいんだろう">投資って何から手を付ければいいんだろう……</h4> <p>2024年から新しいNISAが始まることもあり、世間一般でも「投資をはじめよう!!」みたいな雰囲気が出てきていると思います。 <br />ただ「投資ってどう始めたらいいの?」「色々あるけど結局何がいいの?」という状況の人がほとんどなのではないかと。 <br />そういった「はじめてで戸惑ってしまっている人」のうち会社員にとって向いている投資法って何だろうな?とつらつら考えた際に出てきたのが、入り口としては配当金重視の投資がよいのではないだろうか?という考えでした。</p> <h4 id="やっぱりイメージが付きやすい方がいい">やっぱりイメージが付きやすい方がいい!!</h4> <p>初心者が手を出すべき投資って?と調べるとS&amp;P500に連動する投信や全世界株に連動する投信に入れて放置しておけばいいよという意見が多いと思います。これについては私も有力候補の一つと思っているので、異論は特にありません。</p> <p>ただ、<strong>値上がりを期待する投資対象の成果</strong>を確認するために見る”評価益”は、慣れていない人にとってはやや実感がわきづらいのではないか?とも思っています。増えた減ったは分かるのですが、それがどの程度のインパクトになっているのか?をなかなか実感しづらいな、と。</p> <p>一方配当金であれば、同じように増えた減ったではあるもののキャッシュフローとしての数字なので、普段会社員として給与を受け取っているのと同じような感覚で見ることができます。</p> <p>そういった、<strong>普段給与を受け取る形式と似ている</strong>という点で配当金投資の方がわかりやすいのではないか?と考えています。</p> <p>また、ゴールとして何かしらの数字を設定する際においても、 <br />”金融資産合計”とした場合は1億円と8000万円の違いはなかなか現実感が無く把握しづらいと思いますが、 <br />”年間配当金額"であれば300万円と240万円となり、どの程度の違いがあるのかについても比較的実感がわきやすいのではないでしょうか。 <br />※ここでは仮に、年間配当利回りを3%として換算しています。</p> <h4 id="まずはやってみるでいい">まずはやってみるでいい</h4> <p>投資は色々な選択肢があり、それぞれメリットデメリットあります。ただ、初心者がその大量な情報から自身にとって適切なものを最初から選択するのは、まず無理です。(少なくとも私は無理でした)</p> <p>なので、まずはとりあえずなにか一つ二つを選択して、失敗しても許容できる範囲で始めてみる。 <br />始めてみて「なんか違うな……」となったら方向転換すればいい。 <br />それぐらいの感覚でいいと思っています。</p> <p>そしてその入口として、配当金投資が良いのではないか?というお話でした。</p> <h3 id="実際のところどうなの内製開発グループ内でアンケートを取ってみました">実際のところどうなの?内製開発グループ内でアンケートを取ってみました</h3> <p>ここまでの話とは一転するのですが、私が所属する内製開発グループのメンバーの投資経験ってどうなんだろう?と気になったのでアンケートを取ってみました。結果がこれです!!!</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/rtakigawa/20230531/20230531104739.png" width="370" height="373" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>※上記図では見やすさのために表現を変えていますが、アンケートを取った際の選択肢の記載内容は以下です。 <br /> 1.投資はしてないよ!(401K以外は預金) <br /> 2.インデックス投信つみたてぐらいはやってるよ!(代表的なものとしてはS&amp;P500連動投信) <br /> 3.アクティブ投信に手を出しているよ!(例をあげるとMAFあたりですかね。持ち株会もここに投票いただければと) <br /> 4.個別株にも手を出してるよ! <br />※401Kについては確認したい内容からズレるため、今回は除外させていただきました。 <br />※過去に投資していた、なども含みます。 <br />※複数当てはまる場合は数字の大きい方で回答いただいています。当然、全部検討したうえでインデックスを選択しているかたもいるはずで、数字が大きければいいというわけではありません。</p> <p>いかがでしょうか?証券会社の人間とあって、やっぱり一般よりは投資している人の割合が多い一方、意外と投資未経験の人もいるんだなぁという印象でした。</p> <p>私の勝手なイメージだったのですが、入社する前は「メンバーの半分ぐらいはバリバリ投資してるんだろうなぁ……そこまでやってない人でもつみたて投資はしているんだろうなぁ……投資やってるっていっても適当にやってるような自分がついていけるんだろうか……?」みたいな不安をもっていました。</p> <p>入社してから会話をしていると「いや、そうでもないな?」と感じることが多かったので、今回せっかくの機会ということでアンケートで確認させていただいた形です。 <br />(急だったにも関わらず9割以上のメンバーが回答してくれたので、実態と大きくずれることは無いと思います)</p> <h3 id="おわりに">おわりに</h3> <p>自分は投資経験ないし、システム部門とはいえど証券会社で働くのは流石に無理だろうな……と思って不安に感じているあなた!!</p> <p>アンケート結果のように全く経験ないという人もわりといますし、そもそも経験があったとしても個人差が非常に大きいので、よっぽど詳しいということでもない限りは正直大差はありません!<br />投資とか証券、興味があるんだけど……って方!ぜひお気軽に応募いただければと!<br />当然、投資経験ありの方も大歓迎です!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.monexgroup.jp%2Frecruit%2F" title="マネックスグループ株式会社採用" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <div class="footnote"> <p>※本記事の内容は特定商品の勧誘や特定銘柄の売買を推奨するものではありません。投資は自己責任のもと判断いただけますようよろしくお願いします。 <br />※本記事の内容は私個人の投資経験に基づいて作成したものです。 <br />※もし配当株に投資しようとする場合、配当利回りだけでなく、直近で株価や配当に大きな変動が生じていないか、配当性向の値は問題ないラインか?あたりは確認した方が良いと考えています。一見配当利回りが良いように見えて、減配直前だったみたいなこともあるため。 <br />※ドル円レートは2023/5/29時点のレートを参考に、140円/$として計算しています。</p> </div> rtakigawa 命名の工夫 hatenablog://entry/820878482935767134 2023-05-29T11:13:49+09:00 2023-05-29T11:13:49+09:00 Name こんにちは。システム開発三部 開発推進グループ長の竹中です。 私事ですが、最近子犬をお迎えしました。よく喋る子(寝言もすごい)でかわいいの極みです。柴犬ということもあり、和風な名前をつけました。 名付けといえば、変数や処理名の付け方にも設計者・実装者のセンスが出ると思います。もちろん、命名規則やコーディング規約は守った上でですが、最終的にどんな名前にするのかは悩むところではないでしょうか。社内でも、どんな名前がいいだろうかと相談するチャットが飛んだりしています。 一目で用途がわかるものもあれば、苦肉の策でなんとか名付けたようなものもあり、なかなかおもしろいです。これは上手いなと感じた… <figure class="figure-image figure-image-fotolife mceNonEditable" title="Name"> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/takechan_monex/20230526/20230526172252.jpg" width="1200" height="836" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <figcaption class="mceEditable">Name</figcaption> </figure> <p>こんにちは。システム開発三部 開発推進グループ長の竹中です。</p> <p>私事ですが、最近子犬をお迎えしました。よく喋る子(寝言もすごい)でかわいいの極みです。<br />柴犬ということもあり、和風な名前をつけました。</p> <p>名付けといえば、変数や処理名の付け方にも設計者・実装者のセンスが出ると思います。もちろん、命名規則やコーディング規約は守った上でですが、最終的にどんな名前にするのかは悩むところではないでしょうか。社内でも、どんな名前がいいだろうかと相談するチャットが飛んだりしています。</p> <p>一目で用途がわかるものもあれば、苦肉の策でなんとか名付けたようなものもあり、なかなかおもしろいです。これは上手いなと感じたものや考え方を紹介します。</p> <h4 id="動詞から始める">動詞から始める</h4> <p>なにかを登録する処理であれば"registerFooBar"。これはよくあると思います。</p> <p>特によいなと感じたのが、boolean型で、利用可能な状態を表すときに"isEnabled"と名付けたり、送信待ちな状態を表すときに"isReadyToBeSent"と名付ける方法です。条件分岐も読みやすいです。if (isReadyToBeSent) then ......</p> <p>わざわざ"soshinMachiFlag"(送信待ちフラグ)などとして0(false)だから送っちゃダメだ。1(true)だから送ろう。と脳内変換かけるより楽ですね。</p> <h4 id="無理に省略しない">無理に省略しない</h4> <p>よく目にするのが、アカウント(account)を"act"としたり、金額(amount)を"amt"としたり。<br />その省略、本当に必要でしょうか?一時期、英語圏の地域に常駐してシステム開発に従事していたころ、もはや文章じゃないかという長さのメソッド名もありましたが、可読性はかえって向上していたようにも思います。省略するとかえって分かりづらくなるかも、と立ち止まって考えたいです。</p> <h4 id="数値項目の命名ルールをしっかり定義する">数値項目の命名ルールをしっかり定義する</h4> <p>その数値がただの通し番号なら"number"を使う。<br />その数値そのものが意味を持つなら"code"を使う。<br />その数値がなにかを識別するためのものなら"id"を使う。</p> <p>といったように、しっかり定義しているプロジェクトがありました。他のプロジェクトやシステムでこれらが入った名前を見たときに、どう使われているかな?と度々思い起こしています。</p> <h4 id="おわりに">おわりに</h4> <p>名前の付け方ひとつでも、可読性・保守性に影響し、不具合を作り込むか、品質を作り込むかの分かれ目になると感じています。少しの意識や工夫であっても大事にしたいですね。</p> takechan_monex Ionic React の紹介 hatenablog://entry/4207575160649028406 2023-05-16T14:01:37+09:00 2023-06-28T14:20:59+09:00 こんにちは。マネックス証券で投資情報システムの開発している倉田です。 今回は私が個人的にお勧めしているIonic Reactを使用してクロスプラットフォームアプリを実装したいと思います。 IonicではAngularも使用できますが、今回はReactにフォーカスしたいと思います。 Ionicをざっくりと説明しますと、Web技術を活用してクロスプラットフォームのモバイルアプリを構築するためのフレームワークです。 プラットフォーム固有のコードを書かずに、1つのコードベースでiOSとAndroidの両方のプラットフォームに対応できます。 React Nativeほどメジャーではないのですが、それぞれ… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/nzkkun/20230516/20230516101324.png" width="1200" height="640" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。マネックス証券で投資情報システムの開発している倉田です。 今回は私が個人的にお勧めしているIonic Reactを使用してクロスプラットフォームアプリを実装したいと思います。</p> <blockquote><p>IonicではAngularも使用できますが、今回はReactにフォーカスしたいと思います。</p></blockquote> <p>Ionicをざっくりと説明しますと、Web技術を活用してクロスプラットフォームのモバイルアプリを構築するためのフレームワークです。 プラットフォーム固有のコードを書かずに、1つのコードベースでiOSとAndroidの両方のプラットフォームに対応できます。 React Nativeほどメジャーではないのですが、それぞれのフレームワークには一長一短ありますので、後ほど説明していきたいと思います。</p> <h3 id="Ionic-React-と-React-Native-の違い">Ionic React と React Native の違い</h3> <h1 id="Ionic-React">Ionic React</h1> <p>IonicはWebビューでレンダリングしているため、従来のReact Webアプリの開発に最も似ています。Web版のReactアプリケーションで使用しているCSSやライブラリをそのまま使えます。 また、Ionicで作成したコードはWebアプリケーションとしても実装することができます。</p> <p>しかしWebビューに依存しているためパフォーマンス上の制限が生じる可能性があるので、作成するアプリのパフォーマンスが重要な要素になる場合はReact Native、SwiftやJavaで開発をした方が良いでしょう。</p> <h1 id="React-Native">React Native</h1> <p>一方、React Nativeは、JavaScriptを使用してネイティブコンポーネントをレンダリングし、よりネイティブな外観とパフォーマンスを提供します。その代わり、UI部分はReact Native独自の仕組みで実装されているため、Reactライブラリを使用できないことが多く、またWebアプリケーションとして実装することができません。</p> <p>では、早速アプリを作っていきましょう!</p> <h3 id="アプリの実装">アプリの実装</h3> <h4 id="前提">前提</h4> <ul> <li>macOS、iPhone/iPad を使用</li> <li>最新のXcode がインストールされている</li> <li>Node.js(v16 以降)と npm がインストールされている</li> <li>Ionic CLIのインストール: <code>npm install -g @ionic/cli native-run cordova-res</code></li> </ul> <h4 id="Ionic-React-プロジェクトの作成">Ionic React プロジェクトの作成</h4> <p>公式ドキュメント:<a href="https://ionicframework.com/docs/react/your-first-app">https://ionicframework.com/docs/react/your-first-app</a></p> <p>まずはIonic CLIを使用して新しいIonic Reactプロジェクトを作成します。以下のコマンドを入力して、プロジェクトを作成しましょう。</p> <p><code> ionic start myApp tabs --type=react --capacitor </code></p> <p>これにより、名前が「myApp」の新しいIonic Reactプロジェクトが作成されます。必要に応じてプロジェクト名を変更してください。 プロジェクトが作成されたら、作業ディレクトリをプロジェクトフォルダに移動します。 <code>cd myApp</code></p> <p>次に、以下のコマンドを入力してプロジェクトを実行します。 <code>ionic serve</code> これにより、開発サーバーが起動し、Ionic Reactアプリがブラウザで表示されます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/nzkkun/20230515/20230515093259.png" width="1090" height="792" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>Ionicの初期ページだと物足りないので、ご存知の方も多い Material-UI でログインページを実装しましょう。 まずはライブラリを追加。</p> <pre class="code bash" data-lang="bash" data-unlink>npm install @mui/material @emotion/react @emotion/styled</pre> <p>以下Material-UIの公式テンプレートを <code>/src/App.tsx</code> に上書きします。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">import</span> <span class="synIdentifier">{</span> setupIonicReact <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">'@ionic/react'</span><span class="synStatement">;</span> <span class="synStatement">import</span> Button <span class="synStatement">from</span> <span class="synConstant">&quot;@mui/material/Button&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> TextField <span class="synStatement">from</span> <span class="synConstant">&quot;@mui/material/TextField&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> FormControlLabel <span class="synStatement">from</span> <span class="synConstant">&quot;@mui/material/FormControlLabel&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> Checkbox <span class="synStatement">from</span> <span class="synConstant">&quot;@mui/material/Checkbox&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> Link <span class="synStatement">from</span> <span class="synConstant">&quot;@mui/material/Link&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> Grid <span class="synStatement">from</span> <span class="synConstant">&quot;@mui/material/Grid&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> Box <span class="synStatement">from</span> <span class="synConstant">&quot;@mui/material/Box&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> Typography <span class="synStatement">from</span> <span class="synConstant">&quot;@mui/material/Typography&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> Container <span class="synStatement">from</span> <span class="synConstant">&quot;@mui/material/Container&quot;</span><span class="synStatement">;</span> setupIonicReact<span class="synStatement">();</span> <span class="synType">const</span> App: React.FC <span class="synStatement">=</span> <span class="synStatement">()</span> <span class="synStatement">=&gt;</span> <span class="synStatement">(</span> <span class="synStatement">&lt;</span>Container component<span class="synStatement">=</span><span class="synConstant">&quot;main&quot;</span> maxWidth<span class="synStatement">=</span><span class="synConstant">&quot;xs&quot;</span><span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Box sx<span class="synStatement">=</span><span class="synIdentifier">{{</span> marginTop: <span class="synConstant">8</span><span class="synStatement">,</span> display: <span class="synConstant">&quot;flex&quot;</span><span class="synStatement">,</span> flexDirection: <span class="synConstant">&quot;column&quot;</span><span class="synStatement">,</span> alignItems: <span class="synConstant">&quot;center&quot;</span><span class="synStatement">,</span> <span class="synIdentifier">}}</span> <span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Typography component<span class="synStatement">=</span><span class="synConstant">&quot;h1&quot;</span> variant<span class="synStatement">=</span><span class="synConstant">&quot;h5&quot;</span><span class="synStatement">&gt;</span> Sign <span class="synStatement">in</span> <span class="synStatement">&lt;</span>/Typography<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>TextField margin<span class="synStatement">=</span><span class="synConstant">&quot;normal&quot;</span> fullWidth label<span class="synStatement">=</span><span class="synConstant">&quot;Email Address&quot;</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>TextField margin<span class="synStatement">=</span><span class="synConstant">&quot;normal&quot;</span> fullWidth label<span class="synStatement">=</span><span class="synConstant">&quot;Password&quot;</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>FormControlLabel control<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synStatement">&lt;</span>Checkbox value<span class="synStatement">=</span><span class="synConstant">&quot;remember&quot;</span> color<span class="synStatement">=</span><span class="synConstant">&quot;primary&quot;</span> /<span class="synStatement">&gt;</span><span class="synIdentifier">}</span> label<span class="synStatement">=</span><span class="synConstant">&quot;Remember me&quot;</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Button <span class="synStatement">type=</span><span class="synConstant">&quot;submit&quot;</span> fullWidth variant<span class="synStatement">=</span><span class="synConstant">&quot;contained&quot;</span> sx<span class="synStatement">=</span><span class="synIdentifier">{{</span> mt: <span class="synConstant">3</span><span class="synStatement">,</span> mb: <span class="synConstant">2</span> <span class="synIdentifier">}}</span> <span class="synStatement">&gt;</span> Sign In <span class="synStatement">&lt;</span>/Button<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Grid container<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Grid item xs<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Link href<span class="synStatement">=</span><span class="synConstant">&quot;#&quot;</span> variant<span class="synStatement">=</span><span class="synConstant">&quot;body2&quot;</span><span class="synStatement">&gt;</span> Forgot password? <span class="synStatement">&lt;</span>/Link<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Grid<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Grid item<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Link href<span class="synStatement">=</span><span class="synConstant">&quot;#&quot;</span> variant<span class="synStatement">=</span><span class="synConstant">&quot;body2&quot;</span><span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synConstant">&quot;Don't have an account? Sign Up&quot;</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>/Link<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Grid<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Grid<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Box<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Container<span class="synStatement">&gt;</span> <span class="synStatement">);</span> <span class="synStatement">export</span> <span class="synStatement">default</span> App<span class="synStatement">;</span> </pre> <p><code>ionic serve</code> でログインページが実装されたことを確認しましょう。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/nzkkun/20230515/20230515093501.png" width="686" height="885" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="ネイティブデバイス機能の実装">ネイティブデバイス機能の実装</h4> <p>続いて、ネイティブデバイス機能にアクセスしてみましょう。Ionic 公式ドキュメントではカメラにアクセスしたりしていますが、今回はシンプルにAndroid/iOSを振動させてみます。IonicではCordovaやCapacitorなどのプラグインを使用します。</p> <pre class="code bash" data-lang="bash" data-unlink>npm install @capacitor/haptics</pre> <p>先ほど実装したログイン画面(<code>/src/App.tsx</code>)に振動機能を追加しましょう。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">import</span> <span class="synIdentifier">{</span> Haptics <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">'@capacitor/haptics'</span><span class="synStatement">;</span> <span class="synComment">//インポート</span> <span class="synStatement">&lt;</span>Button <span class="synStatement">type=</span><span class="synConstant">&quot;submit&quot;</span> fullWidth variant<span class="synStatement">=</span><span class="synConstant">&quot;contained&quot;</span> sx<span class="synStatement">=</span><span class="synIdentifier">{{</span> mt: <span class="synConstant">3</span><span class="synStatement">,</span> mb: <span class="synConstant">2</span> <span class="synIdentifier">}}</span> <span class="synSpecial">onClick</span><span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synStatement">()=&gt;</span>Haptics.impact<span class="synStatement">()</span><span class="synIdentifier">}</span> <span class="synComment">//振動機能を追加</span> <span class="synStatement">&gt;</span> Sign In <span class="synStatement">&lt;</span>/Button<span class="synStatement">&gt;</span> </pre> <p>上記のように、たった2行でネイティブデバイス機能にアクセスできます。 使用できるプラグインが豊富にありますので、参考にしてみてください。 - <a href="https://ionicframework.com/docs/native/setup">https://ionicframework.com/docs/native/setup</a> - <a href="https://ionicframework.com/docs/v5/native/stripe">https://ionicframework.com/docs/v5/native/stripe</a></p> <h4 id="お手元のデバイスで動作確認をしましょう">お手元のデバイスで動作確認をしましょう</h4> <p>先ほどのコードをお手元のiPhone/iPadで起動するにはXcodeへビルドする必要があります。以下コマンドを順次に実行してください。</p> <pre class="code bash" data-lang="bash" data-unlink>ionic build ionic cap add ios #または android ionic cap copy ionic cap sync ionic cap open ios #または android</pre> <p>Xcode が起動したらiPhone/iPadをパソコンに繋げ、①で自身のデバイスを選択し、②で実行するとお手元のデバイスにインストールされます。</p> <blockquote><p>デバイスでデベロッパーモードを有効にすることや、③でXcodeのアカウントを作成・選択する必要がありますが割愛します。</p></blockquote> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/nzkkun/20230515/20230515093619.png" width="1200" height="808" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>Sign In クリックでデバイスが振動しました! 後は振動を<strong>while</strong>ループで囲めば電動マッサージ機アプリの完成です!</p> <h3 id="おわりに">おわりに</h3> <p>モバイルアプリ開発をどれだけ簡単に既存の React コードで実装ができるかお分かりいただけたでしょうか。</p> <p>Ionic React の利点を以下のようにまとめました。</p> <table> <thead> <tr> <th>Ionic React </th> <th>React Native </th> </tr> </thead> <tbody> <tr> <td>既存Reactコードをほぼ全て使用できる </td> <td>既存ReactコードのUI部分やライブラリが使用できない</td> </tr> <tr> <td>ReactとWeb(javascript, css)の知識で開発できる</td> <td>React Nativeの学習コストがある</td> </tr> <tr> <td> Webアプリケーションも実装できる</td> <td>Android、iOSモバイルアプリのみ</td> </tr> <tr> <td>Reactのライブラリを使用できる</td> <td>React Nativeのライブラリが充実している</td> </tr> <tr> <td>日本語のドキュメントが充実していない</td> <td> </td> </tr> </tbody> </table> <h4 id="どういういった場合に-Ionic-React-を使うべきか">どういういった場合に Ionic React を使うべきか</h4> <ul> <li>Reactで構築済みのWebアプリがある場合</li> <li>Webアプリ・モバイルアプリを一つのコードベースで構築したい場合</li> <li>パフォーマンスがアプリにとって最重要事項でない場合</li> <li>開発者がReact・Web開発に精通している場合</li> <li>開発速度とコストを重視したい場合</li> </ul> <p>これらの条件が該当する場合、Ionic Reactはハイブリッドモバイルアプリの開発において優れた選択肢になると思いますので、ぜひご検討ください。 </p> <p>参考 <a href="https://ionic.io/blog/ionic-vs-react-native-performance-comparison">https://ionic.io/blog/ionic-vs-react-native-performance-comparison</a></p> <p>システム開発二部 第二プロダクトグループ 倉田</p> nzkkun PlaywrightとmswでスマートなE2Eテストを実現 hatenablog://entry/4207575160645340769 2023-05-12T09:22:40+09:00 2023-05-15T07:11:38+09:00 こんにちは。システム開発一部の吉田です。 最近初めてパーマをかけてみたら元々の天パと掛け合わさって髪がモジャモジャになりました。気に入ってはいますけど取れるのがいつになるのか心配です。 絶賛開発中のフロントエンド領域でPlaywrightとmswを用いたE2Eテスト(End to End Test)の導入をしました。これらのツールの導入に至った経緯と簡単な使い方をご紹介します。 以前書いたモックサーバーの記事の内容も含んでいるので、ご覧になっていない方は以下を参照してみてください。 blog.tech-monex.com E2Eテストのスコープ E2Eテストの概要はCircleCIの記事を見る… <p>こんにちは。システム開発一部の吉田です。</p> <p>最近初めてパーマをかけてみたら元々の天パと掛け合わさって髪がモジャモジャになりました。気に入ってはいますけど取れるのがいつになるのか心配です。</p> <p>絶賛開発中のフロントエンド領域でPlaywrightとmswを用いたE2Eテスト(End to End Test)の導入をしました。これらのツールの導入に至った経緯と簡単な使い方をご紹介します。</p> <p>以前書いたモックサーバーの記事の内容も含んでいるので、ご覧になっていない方は以下を参照してみてください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2022%2F12%2F23%2F100208" title="Prism + Caddyでモックサーバーを作ってみた - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2022/12/23/100208">blog.tech-monex.com</a></cite></p> <h2 id="E2Eテストのスコープ">E2Eテストのスコープ</h2> <p>E2Eテストの概要はCircleCIの記事を見るとなんとなくわかった気になれます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcircleci.com%2Fja%2Fblog%2Fwhat-is-end-to-end-testing%2F" title="E2E (エンドツーエンド) テストとは?" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://circleci.com/ja/blog/what-is-end-to-end-testing/">circleci.com</a></cite></p> <p>一言で言うと、E2Eテストとはユーザーが実際に操作する状況をシミュレートし、アプリケーション全体の動作を検証するテスト手法のことです。</p> <p>アプリケーション全体がテスト範囲なので、結合環境などでフロントエンド~バックエンド~DBを接続した形でE2Eテストを実施することも可能です。 今回のプロジェクトでは実験的な導入のため、E2Eテスト実行時にバックエンドと接続することはしていません。モックのレスポンスを使い、単体環境でのフロントエンドアプリケーションの動作のみを検証することとしました。</p> <h2 id="Playwrightについて">Playwrightについて</h2> <p>Playwrightは、Microsoftが開発したE2Eテストツールで、Chromium、Firefox、Safariの各ブラウザで動作します。それぞれのブラウザに最適化されたAPIを提供し、高速で信頼性の高いテストを実現します。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fmicrosoft%2Fplaywright" title="GitHub - microsoft/playwright: Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/microsoft/playwright">github.com</a></cite></p> <p>Playwrightの他にもSelenium, Puppeteer, CypressなどE2Eテストツールには結構種類があります。 正直な話スケジュールにあまり余裕がなかったので、今回の導入の際に各ツールの比較をあまりしませんでした。 しかし、以下の理由からE2Eテストを導入するのであればPlaywright一択だと考えています。(2023年現在)</p> <ol> <li>複数のブラウザでのテストが容易<br> Playwrightは、複数ブラウザに対応しており、同じコードで複数のブラウザでのテストを行うことができます。Puppeteer, CypressはChromiumベースのブラウザに特化しているため、他のブラウザでのテストが不得手です。</li> <li>高速な実行速度<br> Playwrightは、モダンなJavaScriptエンジンを利用しており、他に比べて高速なテスト実行が可能です。テストの並列実行もサポートされていて、効率的にテストを実行することができます。</li> <li>豊富なAPIと柔軟な設定<br> タッチやマウスでのドラッグ&amp;ドロップなどのネイティブな操作をシミュレートする豊富なAPIを提供しており、ユーザビリティやアクセシビリティのテストにも適しています。また、ネットワークやデバイスのエミュレーションもサポートしています。Playwrightに比べるとPuppeteerはAPIが限定です。</li> <li>単体でVRT(ビジュアルリグレッションテスト)ができる。<br> PlaywrightのAPIを利用することで、簡単にスクリーンショットを取得し、以前のバージョンと比較して差分を検出することができます。これにより、CSSやレイアウトの変更が意図しない影響を及ぼしていないか、また新しい機能の追加が他の部分に悪影響を与えていないかなどを確認することが可能です。(私たちのプロジェクトではVRTは実施していません。)</li> </ol> <p>決め手となったのは1の複数ブラウザでのテスト実施が可能という点。マネックスが定義しているブラウザの推奨環境にはFirefox, Safariも含んでいるので、複数ブラウザでのテストが必須条件でした。</p> <p>2と4の理由についてはこちらの記事の解説がわかりやすかったです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdevelopers.prtimes.jp%2F2023%2F04%2F10%2Fmigrate-from-cypress-to-playwright%2F" title="CypressからPlaywrightに移行しました" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://developers.prtimes.jp/2023/04/10/migrate-from-cypress-to-playwright/">developers.prtimes.jp</a></cite></p> <h2 id="既存のモックサーバーの落とし穴">既存のモックサーバーの落とし穴</h2> <p>検証としていくつかテストケース作成し、導入当初はこれといった問題もなくテスト実行できていました。この時はNuxt3のアプリとPrism + Caddyを使ったモックサーバーを起動した状態にしてPlaywrightのテストを実行する形としていました。</p> <p>けれども、APIからエラーレスポンスが返ってきたときの動作、異常系のテストをするときに問題が発生。上記の構成だとテスト中に正常レスポンスからエラーレスポンスを返すように切り替えることができません。切り替えにはモックサーバーの定義の書き換えと再起動の手動対応が必要なためです。</p> <p>ケースを正常系と異常系のテストを分けて...ということも考えましたが、ケース作成が煩雑になります。手動対応ってスマートじゃないですし。</p> <p>特定のテストケースだけ、かつコードベースでAPIのレスポンスを書き換えるためにmsw(Mock Service Worker)を追加で導入することにしました。</p> <h2 id="mswについて">mswについて</h2> <p>mswは、名前の通りService Workerを用いたモックライブラリです。Service WorkerとはWebページとブラウザの間に位置するスクリプトで、バックグラウンドで実行されるJavaScriptワーカーのことを指します。mswを使うことによってAPIリクエストを捕捉、インターセプトすることでカスタマイズされたレスポンスを返すことができます。これをPrism + Caddyのモックと組み合わせて使い、テストケース単位でレスポンスを書き換えていきます。</p> <p>playwrightのテスト内でmswにアクセスする、テストコードからmswを操作するには以下のライブラリが必要なので注意。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fplaywright-msw" title="playwright-msw" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.npmjs.com/package/playwright-msw">www.npmjs.com</a></cite></p> <p>Playwrightのテスト内で@playwright/testのテストをそのまま使っていた部分を書き換えます。任意のディレクトリにtestUtil.tsに定義します。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">import</span> <span class="synIdentifier">{</span> test <span class="synStatement">as</span> base<span class="synStatement">,</span> expect <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">'@playwright/test'</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synStatement">type</span> <span class="synIdentifier">{</span> MockServiceWorker <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">'playwright-msw'</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> createWorkerFixture <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">'playwright-msw'</span><span class="synStatement">;</span> <span class="synStatement">import</span> handlers <span class="synStatement">from</span> <span class="synConstant">'./handlers'</span><span class="synStatement">;</span> <span class="synStatement">export</span> <span class="synType">const</span> test <span class="synStatement">=</span> base.extend<span class="synStatement">&lt;</span><span class="synIdentifier">{</span> worker: MockServiceWorker<span class="synStatement">;</span> <span class="synIdentifier">}</span><span class="synStatement">&gt;(</span><span class="synIdentifier">{</span> worker: createWorkerFixture<span class="synStatement">(</span>handlers<span class="synStatement">),</span> <span class="synIdentifier">}</span><span class="synStatement">);</span> </pre> <p>テストコードで↑をインポートするように書き替えます。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">import</span> <span class="synIdentifier">{</span> test <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">'./testUtil'</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> rest <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">'msw'</span> test<span class="synStatement">(</span><span class="synConstant">'GET /api/hello returns 400'</span><span class="synStatement">,</span> <span class="synStatement">async</span> <span class="synStatement">(</span><span class="synIdentifier">{</span> page<span class="synStatement">,</span> worker <span class="synIdentifier">}</span><span class="synStatement">)</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> <span class="synComment">// Define the response for the &quot;/api/hello&quot; route</span> worker.use<span class="synStatement">(</span> rest.<span class="synStatement">get(</span><span class="synConstant">'/api/hello'</span><span class="synStatement">,</span> <span class="synStatement">(</span>req<span class="synStatement">,</span> res<span class="synStatement">,</span> ctx<span class="synStatement">)</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> <span class="synStatement">return</span> res<span class="synStatement">(</span> ctx.<span class="synStatement">status(</span><span class="synConstant">400</span><span class="synStatement">),</span> ctx.json<span class="synStatement">(</span><span class="synIdentifier">{</span> error: <span class="synConstant">'Bad Request'</span><span class="synStatement">,</span> <span class="synIdentifier">}</span><span class="synStatement">),</span> <span class="synStatement">);</span> <span class="synIdentifier">}</span><span class="synStatement">),</span> <span class="synStatement">);</span> <span class="synComment">// Run your test</span> <span class="synStatement">await</span> page.<span class="synStatement">goto(</span><span class="synConstant">'http://localhost:3000'</span><span class="synStatement">);</span> <span class="synComment">// ... do some actions, check the result</span> <span class="synIdentifier">}</span><span class="synStatement">);</span> </pre> <p>上記はテストコード内でGET /api/helloにアクセスしたときに400のレスポンスコードが返すようにする例です。</p> <p>ケース内でworkerを引数に追加して、use関数を使ってレスポンスを定義します。Playwrightのテスト実行中にヘッドレスPrismへのリクエストをService Workerがインターセプトしてレスポンスを返し、モックサーバーの再起動をすることなく異常系のテストを実行できます。</p> <h2 id="おわりに">おわりに</h2> <p>モックサーバーにmswを加えたことで、手動の工程を挟むことなく、スマートにE2Eテストを実行できるようになりました。</p> <p>今回紹介したPlaywrightとmswの組み合わせは、環境構築の一例です。 mswはE2Eテストにおいて便利ですが、レスポンスを自分で定義しないといけないという弱点があります。その点、PrismではOpenAPIの定義から自動的にモックサーバーを作ってくれるのでその手間が省けます。 加えて開発中にAPI定義とモックのレスポンスが乖離してしまう...ということもありません。mswを使う上で乖離を防ぐ仕組みは考慮する必要があります。</p> <p>私の所属するチームでは今のところE2Eテストだけmswを使うように周知しています。チーム内の混乱を避けるために、ツールを導入する際には内部でその使い方について認識を合わせておくことが大切かもしれませんね。あとドキュメント化も。</p> <p>VRTやCI/CDで環境をプロビジョニングしてE2Eテスト...などもPlaywrightを使ってやっていきたいと考えています。E2Eテストでの品質保証にご興味のある方もない方も、ぜひ以下の募集をご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.monexgroup.jp%2Frecruit%2F" title="マネックスグループ株式会社採用" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.monexgroup.jp/recruit/">www.monexgroup.jp</a></cite></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/j/jy_admin/20230512/20230512092204.png" width="640" height="640" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> jy_admin 【レポート】第3回 社内アイデアソン hatenablog://entry/4207112889982941517 2023-04-28T18:05:15+09:00 2023-05-24T13:13:22+09:00 こんにちは。お疲れ様です。アイデアソン事務局より2023年2月22日(水)に開催された第3回社内アイデアソンのレポートをお届けします。 マネックス証券システム部門では、2018年度より社内アイデアソンを開催しています。今回は3年ぶりの開催です。エントリーした9組17名のうち、前回開催以降に入社(新卒採用&中途採用)された方の参加は14名でした。入社2か月目のメンバーも参加して、とても新鮮な発表会となりました。 審査員は清明社長、システム部門の役員・部長(計9名)が発表会場(会議室)やオンラインで参加されました。 今回のテーマは「Be the first penguin」 ファーストペンギンとは… <p>こんにちは。お疲れ様です。<br />アイデアソン事務局より2023年2月22日(水)に開催された第3回社内アイデアソンのレポートをお届けします。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokos_blog/20230420/20230420143237.png" width="1200" height="360" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>マネックス証券システム部門では、2018年度より社内アイデアソンを開催しています。<br />今回は3年ぶりの開催です。エントリーした9組17名のうち、前回開催以降に入社(新卒採用&中途採用)された方の参加は14名でした。入社2か月目のメンバーも参加して、とても新鮮な発表会となりました。</p> <p>審査員は清明社長、システム部門の役員・部長(計9名)が発表会場(会議室)やオンラインで参加されました。</p> <h4 id="今回のテーマはBe-the-first-penguin">今回のテーマは「Be the first penguin」</h4> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokos_blog/20230424/20230424145030.jpg" width="640" height="432" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>ファーストペンギンとは、ベンチャー精神を持って行動する個人や企業を、尊敬を込めて呼ぶ言葉として知られています。</p> <p>アイデアの対象は、内製開発している証券基幹システム「GALAXY」または「GALAXYを含む周辺システム・サービス」に関わっていればなんでもアリ。システム化されていない業務改善についても可。<br />一歩先の未来の金融サービスを創造していくために先陣を切って飛び込んでいくペンギンのイメージです。<br />以下、エントリーされた9組の発表になります。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokos_blog/20230420/20230420151540.png" width="1200" height="579" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokos_blog/20230424/20230424105452.jpg" width="702" height="443" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>全発表終了後、無事に終わって一安心。</p> <p> </p> <h4 id="新卒入社1年目2年目コンビが最優秀賞を受賞しました">新卒入社1年目・2年目コンビが、最優秀賞を受賞しました!</h4> <p><strong>「株式を裏付けとするトークン」</strong>(株担保型トークンを発行するビジネス、仕組み、実現される世界線)</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokos_blog/20230424/20230424105207.jpg" width="404" height="303" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>優秀賞は<strong>「SNS情報を用いたセンチメント抽出の試み」</strong>(相場に関するSNSの書込みから世の中の雰囲気を読み解く)</p> <p>特別賞は<strong>「Looking back 私の投資」</strong>(仮想のタイムマシンで、過去の自身の投資履歴を改変して遊び学ぶ)</p> <p>という結果になりました。</p> <h4 id="参加者アンケートのご紹介">参加者アンケートのご紹介</h4> <p>参加者の皆さんに伺った率直な感想を公開します。※ 匿名アンケート 回答者15名</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokos_blog/20230420/20230420160615.png" width="1026" height="452" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokos_blog/20230420/20230420160653.png" width="751" height="452" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>ほとんどの方が「参加して良かった」「何かしら役に立った」と思えたようです。</p> <p> </p> <p>資料準備や調査期間について満足度はいかがでしたか。<br /><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokos_blog/20230420/20230420160711.png" width="752" height="452" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>アドバイスをもらったり相談できる環境について満足度はいかがでしたか。<br /><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokos_blog/20230420/20230420160734.png" width="752" height="452" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>発表時間について満足度はいかがでしたか。<br /><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokos_blog/20230420/20230420160803.png" width="752" height="452" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>事務局からの連絡について満足度はいかがでしたか。<br /><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokos_blog/20230420/20230420160748.png" width="752" height="452" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <h4 id="全体を通してよかった点を教えてください">全体を通してよかった点を教えてください</h4> <ul> <li>各発表から様々な業務の未来を感じ取れること</li> <li>参加者方の発表を通して、自分自身学びとなったのはとてもよかったと感じています</li> <li>初対面の存じ上げない参加者・関係者の方々の顔が知れてよかったです</li> <li>全体的に進行がスムーズであり特に迷うことはなかった(当たり前のように見えて、すごく助かる点)</li> <li>自分のアイデアを多くの人に知っていただけ、また意見をいただけた</li> <li>発表の縛りがなく、自由に発表内容を決められるところがよかった</li> <li>これまで何社か経験をしマネックスに中途で採用されたが、役員や社長の前でプレゼンで提案する機会があったことはこれまでになく、また、発表に際し臆することのない雰囲気を設けていただき、マネックスという会社は風通しの良い会社であるとより感じた</li> <li>なんとか発表にこぎ着けたが、準備する時間がほとんどなく発表を終えるまでかなり緊張していた</li> <li>会場の雰囲気に相当助けられたと思う</li> <li>日頃考えていることを発表する場を提供いただいたこと</li> <li>普段の業務ではどうしても目の前の案件対応のことにフォーカスしてしまいますが、長期的なスパンでサービス/システムを考える機会となり、有意義でした</li> <li>普段の業務でプレゼンの機会が無いので、参加できてよかった</li> <li>準備が周到にされていて、当日の進行がスムーズだった。交代時間を減らして発表時間に充てられそうと思いました</li> <li>「最新技術を使うこと」などの縛りがなかったので、さまざまなアイデアが発表できたのがよかった。技術は手段であり目的ではないので、技術縛りは良くないと思っています</li> <li>発表時間の制限があったのはよかった</li> <li>アイデアソンを通して、社員とコミュニケーションできたこと</li> <li>システム部門の(前回開催時にはいなかった)新たなメンバーの考えが聞けて良かったと思います</li> <li>発表に向けてチーム内で様々な意見交換ができたのが良かったです</li> <li>部を跨いで交流できたのは良かったと思います。チームでゴールに向かって協力して進めるという観点で業務でもいい影響が出ると思いました</li> </ul> <p> </p> <h4 id="全体を通して残念に思った点を教えてください">全体を通して残念に思った点を教えてください</h4> <ul> <li>システム部内でクローズドな会になってしまっているのがもったいないと感じました。もちろん業務都合で各社員の時間を奪う形にはなってしまいますが、全社規模でやっても良いのかなと個人的には思います。(発表者はシステム部限定、視聴者は全社員対象など)</li> <li>優勝の対価をもっと夢のある内容(たとえば優勝賞金一人あたり25万円とか)だと参加者のモチベーションも上がると思うので、もしかしたら参加者人数も増えて大会自体も勢いのある会になるのかなと感じました</li> <li>発表時間については早期に連携が欲しかった(8分は短いので、3人以上を避けるなどの判断があったはずなので)</li> <li>発表中に時間がわかるよう、審査員席の前にiPadでタイマー表示などしてくれると嬉しい</li> <li>鐘について、3箇所でなるのはよいが、1ポイント目は1回、2ポイント目は2回ならすなど違いがわかるとよいかと</li> <li>デモが評価されやすいのはわかるが、今後みんながデモに力を入れすぎて業務が回らなくなるなどが生じる懸念あり(これはそういった問題が顕在化してからでもよいのかもしれないが)</li> <li>少し発表時間が短かった</li> <li>アイデアの練りこみと調査に時間がとれなかったため、今後、通年で実施であれば、事前に開催する月(今回に倣うのであれば2月)で固定したほうが、アイデアの練りこみに時間がかけられるかなとおもいました</li> <li>アイデアソンではあるが、発表に準備でもう少しデータの解析やプロトタイプを作るなど、技術的な裏付けをする時間が欲しかった</li> <li>エントリー数が多いためか、割り当て時間が短かったこと</li> <li>発表の制限時間があるのは良かったですが、8分だと少し短かったです</li> <li>アイデアを問うのであれば、参加者をシステム系に限らなくてもよいのではないかと思った</li> <li>システム投資予算を取るためには、まだまだ発表内容のレベルアップが必要ではありますが、システム投資予算に採用される可能性もあるようなスケジュールだと、より参加者のモチベーションは高まると思いました</li> <li>発表時間については制限幅を設けて欲しかった。発表途中でベルが鳴ってしまい少し焦り早口になってしまった</li> <li>システム部門の方しか参加されていないのはちょっと残念でした。今後可能であれば複数部署に参加していただいて部門跨ってビジネス層のコミュニケーションを取れればいいかなと思っています</li> <li>以前のアイデアソンは、ユーザ部門の人も自由に聴講できていたと思うのですが、今回はなかったでしょうか?案件外でかかわりを持てる&システム部門の人の考えを発信できる貴重な機会なので、次回はオープンにできたらいいなと思いました</li> <li>特にないです</li> <li>発表の準備など、様々な面で準備不足が露呈したと感じたので準備は大事だと痛感しました。ただ、この経験は業務や今後にも活きると思うのでいい経験となりました</li> </ul> <p> </p> <p> </p> <p> </p> <h5 id="第1回2018年度開催のレポートもぜひご覧ください">第1回(2018年度開催)のレポートもぜひご覧ください</h5> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2019%2F08%2F27%2F110310" title="賞金10万円!社内アイデアソン優勝チーム決定&参加者の満足度は?! - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2019/08/27/110310">blog.tech-monex.com</a></cite></p> <h5 id="第2回2019年度開催のレポートもぜひご覧ください">第2回(2019年度開催)のレポートもぜひご覧ください</h5> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2020%2F03%2F04%2F180946" title="【社内アイデアソン】1位を受賞した発表が案件化されました! - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2020/03/04/180946">blog.tech-monex.com</a></cite></p> <h4 id="今回の記念品はオリジナルロゴ入り木製スマホスタンドです">今回の記念品はオリジナルロゴ入り木製スマホスタンドです</h4> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokos_blog/20230420/20230420173416.jpg" width="900" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>充電しながら使えるタイプです。</p> <p> </p> <h4 id="最後に">最後に</h4> <p>アンケートのなかにこんなコメントがありました。</p> <ul> <li>これまで何社か経験をしマネックスに中途で採用されたが、役員や社長の前でプレゼンで提案する機会があったことはこれまでになく、また、発表に際し臆することのない雰囲気を設けていただき、マネックスという会社は風通しの良い会社であるとより感じた</li> </ul> <p> </p> <p>2020年以降に入社された方は、マスクやオンライン会議の影響で同僚の顔もわからず、ランチや飲み会の機会も無く、(歓送迎会もできない、寂しい3年間でした・・・。しっかり打ち上げは開催いたしました!)このアイデアソンを通じて初めて「このひと、こんな顔だったんだ」とか、「〇〇さんて、この人だったのかー」とか、発表以前の気づきもたくさんあったと思います。</p> <p>対面の機会が少なくなってしまっても「風通しのよい会社」という感想を抱いていただけたアイデアソンになって、本当に良かったです。</p> <p>また今回は事務局のメンバーも人数が増え、意見を出し合って協力して開催することができました。通常業務があって、時間をこじあけてのアイデアソンの取り組みは本当に大変だったと思います。参加者の皆さん、審査員の皆さん、本当にお疲れ様でした。</p> <p> </p> <p>この記事を読んで、「次回参加してみようかな」と思っていただけたら幸いです。</p> <p> </p> <div class="entry-writer"> <div class="image-round"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokos_blog/20230421/20230421140027.jpg" width="1200" height="953" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></div> <span class="name">アイデアソン事務局 S</span><span class="job">システム管理部 管理グループ</span></div> yokos_blog 数字で見る内製開発グループ hatenablog://entry/4207112889978140645 2023-04-21T09:31:52+09:00 2023-04-21T09:31:52+09:00 システム開発一部、内製開発グループの池田です。金融業界経験なし、業務知識なし*1の状態で2020年4月に入社し3年経過しました。 前職では前半が受託開発、後半はSES契約で客先に常駐し、Webアプリケーションの開発全般に携わりました。入社後は取引所接続周りの開発に携わり、制度案件のPMを経験し、現在はある商品のシステム更改に挑んでいます。 プライベートでは妻と4歳の娘の3人家族です。 内製開発グループ 早速ですが今回は内製開発グループの紹介をさせていただきたいと思います。 内製開発グループは開発本部のシステム開発一部に属しており、2023年3月末時点で25名が在籍しています。主にGALAXY(… <p>システム開発一部、内製開発グループの池田です。<br />金融業界経験なし、業務知識なし<a href="#f-e0afb771" name="fn-e0afb771" title="「証券決済システムのすべて」等の本をわからないながら読みました。">*1</a>の状態で2020年4月に入社し3年経過しました。</p> <p>前職では前半が受託開発、後半はSES契約で客先に常駐し、Webアプリケーションの開発全般に携わりました。<br />入社後は取引所接続周りの開発に携わり、制度案件のPMを経験し、現在はある商品のシステム更改に挑んでいます。</p> <p>プライベートでは妻と4歳の娘の3人家族です。</p> <h3 id="内製開発グループ">内製開発グループ</h3> <p>早速ですが今回は内製開発グループの紹介をさせていただきたいと思います。</p> <p>内製開発グループは開発本部のシステム開発一部に属しており、2023年3月末時点で<strong>25名</strong>が在籍しています。<br />主にGALAXY(証券基幹システム)のシステム開発・保守を担当しています。<br /><br /></p> <figure class="figure-image figure-image-fotolife mceNonEditable" title="組織図"> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hikedax/20230407/20230407204602.jpg" width="737" height="325" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <figcaption class="mceEditable">組織図</figcaption> </figure> <p>この図にはありませんが、開発本部の横並びにオペレーション本部があり、システムの管理・運用全般、システム基盤に関する業務を統括しています。<br />GALAXYは規模が大きく、1つの部署ですべてを担当することは困難です。<br />その為、インフラ、ミドルウェアといったシステム基盤全般、リリース等の運用全般、オペレーション本部主管となっています。</p> <p>周辺グループの説明は過去の記事をご参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2022%2F11%2F10%2F131703" title="マネックス証券 開発本部の紹介 - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2022/11/10/131703">blog.tech-monex.com</a></cite></p> <p>そんな中にある内製開発グループについて、直近6ヶ月(2022/10~2023/3)のデータから次の数字を確認してみました。<br />(周辺の情報から個人的に確認した数字の為、参考程度に捉えていただきたく存じます)</p> <ul> <li>在宅率</li> <li>営業日に対する有給取得率</li> <li>平均残業時間</li> <li>作業時間の割合</li> </ul> <h4 id="在宅率">在宅率</h4> <p>まずは在宅率です。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hikedax/20230407/20230407210637.jpg" width="625" height="258" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>コロナ前は在宅制度が無かったようですが、私が入社した2020年4月には在宅制度があり今後も継続されることとなりました。<br />一時期は70%前後を推移していたと思いますが、最近は<strong>60%前後</strong>で落ち着いているようです。<br />最近、内製開発グループは月1回程度は出社することとなった為、出社率が徐々に増えるかもしれません。</p> <h4 id="営業日に対する有給取得率">営業日に対する有給取得率</h4> <p>続いて営業日に対する有給取得率です。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hikedax/20230407/20230407210651.jpg" width="624" height="246" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>これは有給の取得具合を共有したいなと思い、出しました。<br />「グループメンバーの有給取得回数」を「グループメンバー数 * 営業日」で割ったものです。<br />大雑把に<strong>5%前後</strong>なので、1ヶ月に1回(20営業日の1日)程、有給を取得しているとみています。</p> <p>実際、私は月に1回は有給を取得するように心がけています。</p> <p>ちなみに厚生労働省の<a href="https://www.mhlw.go.jp/toukei/itiran/roudou/jikan/syurou/22/dl/gaikyou.pdf" target="_blank">令和4年就労条件総合調査の概況</a>では「年次有給休暇の取得率は58.3%」とありますが、内製開発グループの2023年3月末時点の有給取得率は<strong>91%</strong>でした。</p> <h4 id="平均残業時間">平均残業時間</h4> <p>次は平均残業時間です。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hikedax/20230411/20230411205229.jpg" width="622" height="240" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>2022年10月はグループメンバーが多くかかわっているプロジェクトで大きな体制変更があり、平均残業時間が多めになったとみています。<br />(皆さんの頑張りが垣間見えます)<br />平均してみると<strong>19.1h</strong>でした。</p> <p><a href="https://doda.jp/guide/zangyo/#:~:text=Web%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2" target="_blank">dodaの平均残業時間ランキングのWebエンジニア</a>と比較すると、5.8h程下回っていました。</p> <p>内製開発グループ:19.1h<br />Webエンジニア:24.9h</p> <h4 id="作業時間の割合">作業時間の割合</h4> <p>最後に1週間の時間の使い方を役割毎に並べてみます。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hikedax/20230411/20230411205810.jpg" width="556" height="285" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>役割に応じて時間の使われ方は異なりますが、皆さん<strong>80%前後</strong>は主たる開発・保守業務に時間を使えているようです。<br />「社内活動」は社内で催されたアイデアソン2023や、採用活動、OJT等が挙がりました。</p> <p>私はグループのマネージャー(グループの長ではない)を務めさせていただいていることもあり、開発業務の割合が低いとみています。</p> <h4 id="おまけ">おまけ</h4> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hikedax/20230407/20230407211704.jpg" width="911" height="281" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>Fさん、Hさん、私は同じプロジェクトに参画していますので、何かしらイメージが沸けばと思い体制図を貼っておきます。<br />Tさんは現在保守メインの為、体制外になります。</p> <p>この体制図とは別に、オペレーション本部、業務部門の体制が存在します。</p> <p>ちなみに体制図のトップは第一プロダクトグループ(お隣のグループ)のマネージャーで、案件管理(マスタースケジュール策定、各部門との調整等)を担当いただいています。</p> <h3 id="まとめ">まとめ</h3> <p>いかがでしたでしょうか。<br />業務調整が付いているなど一般的な前提はありますが、在宅や有給取得する上で煩わしさを感じないので、このような数字になっているのかなと推測します。</p> <p>私自身、前職の働きっぷりでは子供の行事(幼稚園の入園式など)に出席することはできないと思っていた(出席する発想が無かった)ので、現在の環境はありがたく感じています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.monexgroup.jp%2Frecruit%2F" title="マネックスグループ株式会社採用" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p><div class="footnote"> <p class="footnote"><a href="#fn-e0afb771" name="f-e0afb771" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">「証券決済システムのすべて」等の本をわからないながら読みました。</span></p> </div> hikedax CI/CDで脆弱性のチェックを行う hatenablog://entry/4207112889976441435 2023-04-07T12:00:00+09:00 2023-04-24T08:48:05+09:00 CI/CDでOSSの脆弱性をチェックする仕組みについて検討します。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230330/20230330104025.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>マネックス証券 システム管理部のHです。<br></p> <h3 id="はじめに">はじめに</h3> <p>日々アプリケーション開発をする上で使用しているOSSの脆弱性というのは気にすべきことではありますが、開発作業に集中しているとOSSの脆弱性を確認するというところまではなかなか手が届かないことと、インターネットに出ない閉じたネットワーク内でのシステムだからいいや・・・と思うことがあるかと思います。<br> そういった状況で自分の代わりに自動でチェックする仕組みがあるといいなと思い、CI/CD内で脆弱性のチェックを行う仕組みを検討します。</p> <h3 id="やりたいこと">やりたいこと</h3> <p>CI/CDとしては、AWS CodePipelineを使用し、パイプライン内のビルドツールとしてAWS CodeBuildを使用します。今回はビルドの確認でAWSリソースへのリリースは行わないので、Deployステージは作成しません。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230330/20230330110824.png" width="361" height="421" loading="lazy" title="" class="hatena-fotolife" style="width:300px" itemprop="image"></span></p> <h3 id="どうやって実現するか考える">どうやって実現するか考える</h3> <h4 id="作成するアプリケーションについて">作成するアプリケーションについて</h4> <ul> <li>Spring BootでシンプルなREST APIを作成します。「<a href="http://localhost:8080/data">http://localhost:8080/data</a>」にアクセスすると、下記のようなレスポンスを返します。</li> </ul> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span>&quot;<span class="synStatement">id</span>&quot;:<span class="synConstant">1</span>,&quot;<span class="synStatement">description</span>&quot;:&quot;<span class="synConstant">サンプルデータ</span>&quot;<span class="synSpecial">}</span> </pre> <ul> <li>脆弱性のチェックについては、OWASP(Open Worldwide Application Security Project)が提供している<a href="https://owasp.org/www-project-dependency-check/">Dependency Check</a>を利用します。<br> OWASPはセキュリティに関するオープンソース・ソフトウェアコミュニティです。</li> </ul> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fowasp.org%2Fwww-chapter-japan%2F" title="OWASP Japan | OWASP Foundation" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://owasp.org/www-chapter-japan/">owasp.org</a></cite></p> <h3 id="実装してみる">実装してみる</h3> <p>実装してみます。Spring BootアプリケーションとCodeBuildで使用するbuildspec.ymlを作成します。</p> <h4 id="Spring-Bootアプリケーションの作成">Spring Bootアプリケーションの作成</h4> <p>Spring BootアプリケーションはGradleプロジェクトとして作成します。</p> <h5 id="buildgradle">build.gradle</h5> <ul> <li>Dependency-Checkプラグインの指定</li> </ul> <pre class="code lang-groovy" data-lang="groovy" data-unlink>id <span class="synConstant">'org.owasp.dependencycheck'</span> version <span class="synConstant">'8.2.1'</span> </pre> <ul> <li>dependencies<br> 脆弱性があるライブラリとして、Log4j2を追加します。(まあ他にも検出されるのですが・・・)</li> </ul> <pre class="code lang-groovy" data-lang="groovy" data-unlink>implementation <span class="synConstant">'org.apache.logging.log4j:log4j-core:2.15.0'</span> </pre> <ul> <li>Dependency-Checkの設定</li> </ul> <p><a href="https://jeremylong.github.io/DependencyCheck/dependency-check-gradle/configuration.html">ここ</a>を参考にDependency-Checkの設定を入れます。<br> 注意するポイントとして、autoUpdateをfalseにしています。<br> このフラグは、ローカルに保存したNVD CVE/CPEデータの自動更新を有効にするかどうかというもので、本来false(自動更新しない)というのは推奨されないのですが、後述する理由によりこの値を設定しています。<br> formatはチェック結果を出力するフォーマットを選択することができます。<br> 選択肢としてはHTML、XML、CSV、JSON、JUNIT、ALLがあります。<br> 今回はローカルではHTMLを使用し、CI/CDではJUNITを使用するのでALLにします。<br> dataはNVD CVE/CPEデータを保存する場所で、今回このデータをCodeCommitに保存するので、場所を指定しています。</p> <pre class="code lang-groovy" data-lang="groovy" data-unlink>dependencyCheck { autoUpdate = <span class="synConstant">false</span> format = <span class="synConstant">'ALL'</span> data { directory = rootProject.file(<span class="synConstant">&quot;data&quot;</span>).absolutePath } } </pre> <h5 id="Spring-Bootアプリケーション">Spring Bootアプリケーション</h5> <p>コントローラクラスを一つ作り、エンドポイント(/data)の実装をします。</p> <ul> <li>Controller.java</li> </ul> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@RestController</span> <span class="synType">public</span> <span class="synType">class</span> Controller { <span class="synPreProc">@GetMapping</span>(<span class="synConstant">&quot;/data&quot;</span>) <span class="synType">public</span> Data getData() { <span class="synStatement">return</span> <span class="synStatement">new</span> Data(<span class="synConstant">1</span>, <span class="synConstant">&quot;サンプルデータ&quot;</span>); } <span class="synPreProc">@AllArgsConstructor</span> <span class="synPreProc">@Getter</span> <span class="synType">class</span> Data { <span class="synType">int</span> id; String description; } } </pre> <h4 id="buildspecymlの実装">buildspec.ymlの実装</h4> <p>CodeBuildで使用するbuildspec.ymlについて実装します。dependencyCheckExampleフォルダ配下に、上記に記載したSpring BootのGradleプロジェクトがあるとします。<br> GradleのdependencyCheckAnalyzeタスクで脆弱性チェックを行います。JUnit形式で出力したファイルをCodeBuildのレポートとして、設定します。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">version</span><span class="synSpecial">:</span> <span class="synConstant">0.2</span> <span class="synIdentifier">phases</span><span class="synSpecial">:</span> <span class="synIdentifier">install</span><span class="synSpecial">:</span> <span class="synIdentifier">runtime-versions</span><span class="synSpecial">:</span> <span class="synIdentifier">java</span><span class="synSpecial">:</span> corretto17 <span class="synIdentifier">build</span><span class="synSpecial">:</span> <span class="synIdentifier">on-failure</span><span class="synSpecial">:</span> CONTINUE <span class="synIdentifier">commands</span><span class="synSpecial">:</span> <span class="synStatement">- </span>echo <span class="synConstant">&quot;build&quot;</span> <span class="synStatement">- </span>cd dependencyCheckExample <span class="synStatement">- </span>./gradlew build dependencyCheckAnalyze ⇐ここで脆弱性チェック <span class="synIdentifier">reports</span><span class="synSpecial">:</span> <span class="synIdentifier">junit-report</span><span class="synSpecial">:</span> <span class="synIdentifier">file-format</span><span class="synSpecial">:</span> <span class="synConstant">&quot;JUNITXML&quot;</span> <span class="synIdentifier">files</span><span class="synSpecial">:</span> <span class="synStatement">- </span>dependencyCheckExample/build/reports/dependency-check-junit.xml </pre> <h4 id="CodePipelineの実装">CodePipelineの実装</h4> <p>ステージはSource、Buildとします。SourceはCodeCommitからソースを取得します。BuildはCodeBuildのプロジェクトを実行します。</p> <h4 id="CodeBuildの実装">CodeBuildの実装</h4> <p>CodeBuildはbuildspec.yml内の処理を実行します。</p> <h3 id="確認してみる">確認してみる</h3> <h4 id="まずはローカルで">まずはローカルで。</h4> <p>まずはローカルで確認します。<br> autoUpdateをfalseに設定しているので、手動で脆弱性データベースの作成を行います。自動ではなく手動にしたのは、CodeBuildでdependencyCheckAnalyzeタスクを実行した際に、CISAやNISTから脆弱性データのファイルダウンロードに失敗することがあるためです。ローカルでは失敗しないので、今回はローカルで脆弱性データベースの作成をして、そのファイルをCodeCommitにコミットします。CodeBuildではそのファイルを使用してチェックするようにします。</p> <pre class="code bash" data-lang="bash" data-unlink>./gradlew dependencyCheckUpdate</pre> <p>大体5分位でデータベースが作成されました。<br> 次に脆弱性のチェックを行います。</p> <pre class="code bash" data-lang="bash" data-unlink>./gradlew dependencyCheckAnalyze</pre> <p>数秒で終わります。その際に脆弱性があれば以下に対象のOSSとCVE番号が表示されます。期待通りLog4j2が検出されました。もう一つのはSpring Bootの依存関係に入っているライブラリです。ちなみにSpring BootのGithubでIssueを見ると、検出されたもののSpring Boot自体に脆弱性はなく、誤検知の可能性が高いとのことです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230330/20230330151826.png" width="1200" height="379" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>詳細は作成されるHTMLでも確認できます。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230330/20230330152002.png" width="1200" height="688" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>脆弱性の情報も出力されます。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230330/20230330152309.png" width="1200" height="516" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ちなみに上記のコンソールでは、Gradleタスクは正常終了していますが、CVSSスコア(脆弱性の深刻度)により異常終了させることもできます。これにより脆弱性の検知がしやすくなります。</p> <ul> <li>build.gradleにfailBuildOnCVSSの設定を追加。 failBuildOnCVSSにはCVSSスコアのしきい値を設定します。しきい値を超えた脆弱性を検出した際にGradleのタスクを失敗させます。今回は9(緊急)を設定しています。<br> CVSSスコアについては<a href="https://www.ipa.go.jp/security/vuln/CVSSv3.html">ここ</a>が参考になるかと思います。</li> </ul> <pre class="code lang-groovy" data-lang="groovy" data-unlink>dependencyCheck { autoUpdate = <span class="synConstant">false</span> format = <span class="synConstant">'ALL'</span> failBuildOnCVSS = <span class="synConstant">9</span> <span class="synComment">// ⇐ここを追加</span> data { directory = rootProject.file(<span class="synConstant">&quot;data&quot;</span>).absolutePath } } </pre> <p>再度脆弱性チェックを行います。Gradleタスクは失敗し、失敗した理由が表示されます。</p> <pre class="code bash" data-lang="bash" data-unlink>./gradlew dependencyCheckAnalyze</pre> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230330/20230330155440.png" width="1200" height="550" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="CodePipelineで実行する">CodePipelineで実行する。</h4> <p>次にCodePipelineで実行します。 failBuildOnCVSSは設定しない状態にします。<br> CodeBuildのビルドログには以下の表に表示されます。まあコンソールと同じように脆弱性の情報が表示されます。(当たり前か・・・)</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230330/20230330162655.png" width="1200" height="504" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>JUnit形式で出力した情報はレポートから確認することができます。円グラフや一覧表で見れるので、コンソールやHTMLよりも見やすい感じがしますね。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230330/20230330163239.png" width="1200" height="399" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230330/20230330163356.png" width="1200" height="850" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ちなみにCodePipelineは正常終了しています。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230330/20230330163004.png" width="1200" height="798" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>今度はfailBuildOnCVSSを設定して、CodeBuildを実行します。(失敗します)ビルドログはローカルで失敗させた時と同様のログが表示されます。(こちらも当たり前か・・・)<br> レポートの表示はCodeBuild正常時と特に変わりません。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230330/20230330165109.png" width="1200" height="505" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>CodePipelineではBuildで失敗します。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20230330/20230330165816.png" width="1200" height="806" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="おわりに">おわりに</h3> <p>CI/CDに組み込むことで、いち早く脆弱性を検知することができます。<br> ただ今回のケースにおいては、脆弱性データベースをCI/CD内では更新できなかったり、ビルドの設定次第では脆弱性チェックに引っかかった場合にその脆弱性を解決しない限り他の機能のリリースができないことにもなりうるため、使い方については十分検討する必要があるかと思います。</p> <div style="text-align: center;"> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hamo2020/20200830/20200830084302.png" width="118" height="200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> </div> hamo2020 オープンソースのコードを読もう hatenablog://entry/4207112889975348903 2023-03-31T10:46:52+09:00 2023-03-31T10:46:52+09:00 こんにちは、内製開発グループの山下です。 プログラミング入門 私はシニア技術者なので、ビギナー、つまりプログラム開発の経験が浅い方のサポートをする機会があります。 私が若いころは情報が少なくて苦労しましたが、今の若い方は逆な気がします。キーワード検索をすると大量のコンテンツがヒットし、親切な動画まで揃っています。限られた時間のなかで、その中から有益な情報を汲み取るのに苦労される場合も多いのではないでしょうか。 という今の状況にマッチするかはわかりませんが、最初はシンプルにコード読みから始めてみませんか?という話を書いてみます。 コード読みの勧め 日本語が理解できても、良い日本語の文章を書けるわ… <figure class="figure-image figure-image-fotolife mceNonEditable" title="source code"> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/toshio_yamashita/20230327/20230327181932.jpg" alt="" width="640" height="425" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> </figure> <p>こんにちは、内製開発グループの山下です。</p> <h3 id="プログラミング入門">プログラミング入門</h3> <p>私はシニア技術者なので、ビギナー、つまりプログラム開発の経験が浅い方のサポートをする機会があります。</p> <p>私が若いころは情報が少なくて苦労しましたが、今の若い方は逆な気がします。キーワード検索をすると大量のコンテンツがヒットし、親切な動画まで揃っています。限られた時間のなかで、その中から有益な情報を汲み取るのに苦労される場合も多いのではないでしょうか。</p> <p>という今の状況にマッチするかはわかりませんが、最初はシンプルにコード読みから始めてみませんか?という話を書いてみます。</p> <h3 id="コード読みの勧め">コード読みの勧め</h3> <p>日本語が理解できても、良い日本語の文章を書けるわけではありません。ある程度の日本語の文章を読み、できれば名文を多く読み、日本語の言語感覚を身に着ける必要があるのではないでしょうか。</p> <p>なので良いコードを読み、コード感覚的なものを身につけよう、という趣向です。</p> <h3 id="お勧めしたい基本ループ">お勧めしたい基本ループ</h3> <p>私のお勧めするプログラミング入門は、シンプルかつ古典的です。</p> <ol> <li>概念を理解する</li> <li>評判の良さそうな入門本を買う</li> <li>入門本をざっくり眺める</li> <li>自分のレベルに合ったサンプルコードを読む</li> <li>コードで不明な部分を入門本で確認する</li> <li>4. ~ 5. を繰り返す</li> </ol> <p>以下、もう少し説明させてください。</p> <h4 id="1-概念を理解する">1. 概念を理解する</h4> <p>まず 1. ですが「漫画でわかるJava」みたいな超初心者向けの本を誰かから借りて読んでも良いでしょう。更にお勧めはネット動画を検索することです。Udemy など学習サイトのセールで講座を購入してもよいでしょう。</p> <p>ここではプログラミング自体ではなく、その前提知識を学びます。</p> <p>これから利用しようとしているプログラミング環境はどんなものか。どんな歴史があって、どんな目的で用意されたものなのか。この環境を利用する人は、どんな感じで開発しているのか。動画でいろいろ操作しているのを、ああ、そんな感じで開発しているのね、と視聴する感じ。</p> <p>雰囲気だけつかめればいいので、まずカタチからはいりましょう。</p> <h4 id="2-入門本を買う">2. 入門本を買う</h4> <p>さていよいよ入門ということで、文法などしっかり説明されたバイブル的な入門本を選んで買いましょう。評判が良くて、目次がしっかりしている感じの本。必ずしも紙の本ではなく、ネット上の定評あるコンテンツでもかまいません。JavaScript ならば以下は鉄板ですし、</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdeveloper.mozilla.org%2Fja%2Fdocs%2FWeb%2FJavaScript" title="JavaScript | MDN" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://developer.mozilla.org/ja/docs/Web/JavaScript">developer.mozilla.org</a></cite></p> <p>TypeScript なら以下は素晴らしいコンテンツですよね。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftypescriptbook.jp%2F" title="はじめに | TypeScript入門『サバイバルTypeScript』" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://typescriptbook.jp/">typescriptbook.jp</a></cite></p> <p><br />サバイバルTypeScript<br /><br />ただ個人的には、ここではあえて紙の本をお勧めしたいです。理由は後述します。</p> <h4 id="3-入門本をざっくり眺める">3. 入門本をざっくり眺める</h4> <p>私の個人的な体験ですが、入門本をしっかり読んで理解しよう!などと思うと、わりと挫折しがちです。読めば読むほど、疑問が増えていきます。自分の知識の不足にどんどん気がつく、知らない用語がどんどん増えていく。それで良いのです。ソクラテスさんが言う「無知の知」って領域ですね。</p> <p>この段階は本の下見。大事なのは目次と、実際の本文の初めの概説をざっくり読んで、本の構成を掴んでおくことです。何か調べたくなったら、本のどのあたりに説明があったかな?となんとなく思い出せるくらいが理想です。</p> <h4 id="4-サンプルコードを読む">4. サンプルコードを読む</h4> <p>さて、ここが今日のポイントですね。</p> <p>まず最近の入門本ですが、URL が記載されていて、サンプルコードがダウンロードできるものもあります。これはラッキーで、そのコードを最初に眺めれば、本の説明とうまく噛み合って理解が進むでしょう。</p> <p>そうでない場合も大丈夫。いまネット上にはオープンソースの良質なコードが溢れています。学びたい言語と sample などのキーワードを付与して検索すると、様々なプロジェクトを発見できます。入門用に用意されたものも多いですし、小さなツールやライブラリから読み始めるのもよいでしょう。</p> <p>特に JavaScript の場合、<a href="https://codepen.io/">CodePen</a>  のようなブラウザ上でコードを参照しつつ、修正や実行もできる開発サイトもお勧めです。こういったサイトでまずサンプルコードを探すのが良いかとおもいます。</p> <p>とはいえ急に探すのは難しいとおもわれますので、JavaScript 用によく紹介されるサンプルコードをご紹介します。まず以下のサイトですが、機能ごとにまとまっているので読みやすいのではないでしょうか。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.30secondsofcode.org%2F" title="30 seconds of code" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.30secondsofcode.org/">www.30secondsofcode.org</a></cite></p> <p>また以下からお勧めのリポジトリが検索できます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2F30-seconds%2F30-seconds-of-code%2Ftree%2Fmaster%2Fsnippets" title="30-seconds-of-code/snippets at master · 30-seconds/30-seconds-of-code" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/30-seconds/30-seconds-of-code/tree/master/snippets">github.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdev.to%2Fmadza%2F21-github-repositories-to-become-a-javascript-master-5bpa" title="21 GitHub Repositories to Become a JavaScript Master 📚🚀" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://dev.to/madza/21-github-repositories-to-become-a-javascript-master-5bpa">dev.to</a></cite></p> <p>上記のように GitHub 上のリポジトリを参照する際に、個人的に素晴らしいとおもうのが以下のサービスです。すごくコードが読みやすくなります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fforest.watch.impress.co.jp%2Fdocs%2Fserial%2Fyajiuma%2F1306113.html" title="GitHubのURLをちょろっと書き換えるだけでコードを「Visual Studio Code」で閲覧できる素敵なサービスが話題に/トグルするブックマークレットも用意されている【やじうまの杜】" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://forest.watch.impress.co.jp/docs/serial/yajiuma/1306113.html">forest.watch.impress.co.jp</a></cite></p> <h4 id="5-本で確認する">5. 本で確認する</h4> <p>さてコードを眺めていると、見慣れない表記が出てくるとおもいます。そうしたら、入門本を探して、説明を読んでみましょう。この探す行為は、紙の本のほうがスムーズにできるとおもいます。本のどのあたりか、ざっくり感覚的に覚えており、またページをパラパラとめくって探しやすい。</p> <p>個人的な話で恐縮ですが、私は電子書籍が大好きで、漫画も小説も電子版を購入し、お気に入りの古い本は電子書籍で買い直したりしています。でも、入門本だけは紙で買います。</p> <p>見慣れない表記は、すぐにネット検索しても良いかもしれません。しかし、いったん入門本の該当ページを開き、読み、不明な点をもう少し明確にしてから検索するのは良い方法です。より具体的なキーワード指定ができ、より短時間で必要な情報を得られ、理解できるでしょう。急がば回れ、という感じでしょうか。</p> <h4 id="6-繰り返す">6. 繰り返す</h4> <p>以上の流れ、いちど回り始めたらもう安心です。だんだんとより大きな、複雑なサンプルコードを読み、動作を理解できるようになるでしょう。</p> <p>また様々な記述をみて、好ましい書き方や、あまり好きになれない書き方を眺めるうち、自分なりのコード感覚的なものができてくるとおもいます。</p> <h3 id="おまけ">おまけ</h3> <p>コードを読み進めていくと、よく出てくる処理の組み方や、わかりやすい構成があるとおもいます。なので適当なタイミングで、以下のようなアルゴリズムやデザインパターンの資料を読むと、更に自分の知識が整理されて良いと思います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftrekhleb%2Fjavascript-algorithms" title="GitHub - trekhleb/javascript-algorithms: 📝 Algorithms and data structures implemented in JavaScript with explanations and links to further readings" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/trekhleb/javascript-algorithms">github.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fja.wikipedia.org%2Fwiki%2F%25E3%2583%2587%25E3%2582%25B6%25E3%2582%25A4%25E3%2583%25B3%25E3%2583%2591%25E3%2582%25BF%25E3%2583%25BC%25E3%2583%25B3_(%25E3%2582%25BD%25E3%2583%2595%25E3%2583%2588%25E3%2582%25A6%25E3%2582%25A7%25E3%2582%25A2)" title="デザインパターン (ソフトウェア) - Wikipedia" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2)">ja.wikipedia.org</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.oreilly.co.jp%2Fbooks%2F9784873116181%2F" title="JavaScriptデザインパターン" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.oreilly.co.jp/books/9784873116181/">www.oreilly.co.jp</a></cite></p> <h3 id="また懐古話ですが">また懐古話ですが</h3> <p>私がプログラミングに熱中したのはまだ学生時代、世の中ではパソコン通信が一般化しつつあり、インターネットはまだアカデミックな世界に閉じていた頃でした。</p> <p>このころ、Linux の前身となる MINIX というオペレーティングシステムがありました。教育用に開発されたソフトウェアなため、解説本と共にソースコードが丸ごとオープンになっており、自由に閲覧できたのです。良質なサンプルコードに飢えていた私たちにとって、MINIX 解説本とコードは、それこそバイブルのように貴重なものでした。</p> <p>※ 本にフロッピーディスク(今だとUSBメモリみたいなもの)が付属しており、ソースファイルが参照できました</p> <p>つい先日、この MINIX のコードが GitHub にあることを知り、とても懐かしかったです。実はこのサイトにより初心を少し思い出したため、この投稿を書いていたりします。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fo-oconnell%2Fminixfromscratch" title="GitHub - o-oconnell/minixfromscratch: Development and compilation setup for the book versions of MINIX (2.0.0 and 3.1.0) on QEMU" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/o-oconnell/minixfromscratch">github.com</a></cite></p> <p>さきほどご紹介した github1s.com ドメインの技を使うと、Web ブラウザ上で快適にコードが読めてしまいます。なんて素晴らしい!学生時代の私にこのサイトを見せたら、あまりに楽しくて、ずっと眺めていて、きっと留年していたことでしょう。。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/toshio_yamashita/20230327/20230327183639.png" alt="Minix on GitHub" width="1113" height="850" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <h3 id="最後に">最後に</h3> <p>以上、ネットに豊富にある良質なコードをどんどん読んでいこうよ、という話を投稿させていただきました。世の中には美しいコードがたくさんあって、楽しいです。それを気軽に選べて、見れて、良い時代になったなぁ、と思っています。</p> <p>読んでいただきありがとうございました。マネックスの採用にご興味のある方は、ぜひ以下募集をご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.monexgroup.jp%2Frecruit%2F" title="マネックスグループ株式会社採用" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.monexgroup.jp/recruit/">www.monexgroup.jp</a></cite></p> <p> </p> toshio_yamashita 2022年度エンジニアブログアワード hatenablog://entry/4207112889974371977 2023-03-24T18:15:05+09:00 2023-03-24T18:15:05+09:00 2022年度に、エンジニアブログでよく読まれた記事をランキング形式で紹介します。 <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kingblogger/20230324/20230324171603.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><span style="color: #4c4c4c; font-family: 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', メイリオ, Meiryo, Osaka, 'MS Pゴシック', 'MS PGothic', sans-serif; font-size: 15.3333px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.46px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">こんにちは、システム開発三部長 兼 マネックス・ラボ長の上川です。</span></p> <p>2022年度もそろそろ終わりますね。</p> <p>先日子供の卒業式があったのですが、今年はもうすでに桜が満開でいい写真が撮れてよかったものの、おそらく4月の入学式シーズンにはもう散ってしまっていそうです。</p> <p>今年は春が来るのが早い。</p> <h3 id="エンジニアブログを振り返る">エンジニアブログを振り返る</h3> <p>年度末ですので、エンジニアブログの振り返り記事でも書こうかなと思ったら、昨年も同じことしていました(笑)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2022%2F04%2F01%2F134714" title="エンジニアブログを振り返ってみる - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2022/04/01/134714">blog.tech-monex.com</a></cite></p> <p>同じことを書いても仕方ないので、今回はよく読まれている記事をランキング形式で紹介しようかと思います。</p> <p>2020年と2021年頃には別の人がランキング記事を書いていますが、さらに2年ほど経過して、強い記事は新たに現れてきたのでしょうか。</p> <h4 id="年間記事数">年間記事数</h4> <p>2022年度(2022年4月~2023年3月)の総投稿記事数はここまで41本。</p> <p>この記事を含めると年間42本でした。</p> <h4 id="PV数">PV数</h4> <p>全体のPV数の伸びですが、このエンジニアブログは2018年10月に開始しているので、2019年1~3月の3ヶ月間のPV数を1としたきに、各年の1~3月のPV数がどれくらいになるか比較してみます。</p> <ul> <li>2019年:1</li> <li>2020年:4.1</li> <li>2021年:9.3</li> <li>2022年:11</li> <li>2023年:20.9</li> </ul> <p>ということで、2021年度はほぼ横ばいだったPVが、2022年度は一気に2倍に伸びています。</p> <p>2022年度は、執筆者を少しずつ増やしているので、その分記事の内容もバラエティに富んできているのが一つの理由かもしれません。</p> <h4 id="閲覧数ランキング">閲覧数ランキング</h4> <p>単純に年間のPV数ランキングにしてしまうと、年初に書かれた記事の方が有利になってしまうので、どういう集計にしようか迷いましたが、今回は過去記事も含めた全記事の中で、直近2023年3月1日~23日の間にPV数の多かった順に並べてみます。</p> <p>3月中に投稿された記事が不利になってしまいますが、そこはごめんなさい。</p> <h5 id="第1位">第1位</h5> <p>CSVの国際標準 RFC 4180 と JSONの国際標準 RFC 8259 をいまさら読みなおしてみた(前編)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2021%2F03%2F26%2F160000" title="CSVの国際標準 RFC 4180 と JSONの国際標準 RFC 8259 をいまさら読みなおしてみた(前編) - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2021/03/26/160000">blog.tech-monex.com</a></cite></p> <p>2021年に投稿された小田切さんの記事が今回もNo1となりました。</p> <p>内容が濃く、よく読まれてSEO的にも上位表示されるようになると、長期的に強い記事になりますね。</p> <h5 id="第2位">第2位</h5> <p>SwiftのコンパイラでThe compiler is unable to type-checkのエラーが出た時は、型を明示するといいかもしれない</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2020%2F03%2F13%2F163130" title="SwiftのコンパイラでThe compiler is unable to type-checkのエラーが出た時は、型を明示するといいかもしれない - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2020/03/13/163130">blog.tech-monex.com</a></cite></p> <p>2020年投稿の、佐藤さんの記事がNo2。</p> <p>Swiftで困った人の助けになっていればうれしいですね。</p> <h5 id="第3位">第3位</h5> <p>AWS資格の再認定</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2022%2F11%2F04%2F225149" title="AWS資格の再認定 - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2022/11/04/225149">blog.tech-monex.com</a></cite></p> <p>AWS関連の記事はよく読まれていますが、AWS資格の話題なのでよく検索にヒットするのかもしれません。</p> <h5 id="第4位">第4位</h5> <p>GoogleSignInを本番環境向けに設定して申請したら大変だった話</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2021%2F10%2F08%2F000000" title="GoogleSignInを本番環境向けに設定して申請したら大変だった話 - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2021/10/08/000000">blog.tech-monex.com</a></cite></p> <p>佐藤さんの記事が2本目のランクイン。</p> <p>お悩み解決系は、同じところで悩む人の光になるので、PV上がりますね。</p> <h5 id="第5位">第5位</h5> <p>CSVの国際標準 RFC 4180 と JSONの国際標準 RFC 8259 をいまさら読みなおしてみた(後編)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2021%2F04%2F02%2F160000" title="CSVの国際標準 RFC 4180 と JSONの国際標準 RFC 8259 をいまさら読みなおしてみた(後編) - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2021/04/02/160000">blog.tech-monex.com</a></cite></p> <p>No1の小田切さんの記事の後編です。</p> <p>こちらも長期的に強い記事。</p> <h5 id="第6位">第6位</h5> <p>AWS X-Rayを使ってAPIをトレースしよう!!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2021%2F09%2F03%2F170000" title="AWS X-Rayを使ってAPIをトレースしよう!! - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2021/09/03/170000">blog.tech-monex.com</a></cite></p> <p>Hさんの記事がランクイン。</p> <p>AWSのサービスを使ってみた系は、ニーズ高めですよね。</p> <h5 id="第7位">第7位</h5> <p>【AWS ECS】ECS+Fargateのセキュリティまとめ</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2021%2F11%2F05%2F182327" title="【AWS ECS】ECS+Fargateのセキュリティまとめ - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2021/11/05/182327">blog.tech-monex.com</a></cite></p> <p>昨年のAWS Innovate にも登壇した田代さんの記事が7位にランクイン。</p> <p>ECS Fargateネタなので、これからコンテナ化していこうとしている人によく読まれているのでしょうか。</p> <p>ちなみに、田代さんが登壇したイベントはこちらです。</p> <p>今からでもオンデマンド視聴できますので、ご興味がある方はぜひ。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fevents%2Faws-innovate%2Fapj%2Fmodern-apps%2F" title="AWS Innovate - Modern Applications Edition | AWS" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://aws.amazon.com/jp/events/aws-innovate/apj/modern-apps/">aws.amazon.com</a></cite></p> <h5 id="第8位">第8位</h5> <p>Wiresharkを使ったパケット解析の基本</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2020%2F06%2F19%2F172659" title="Wiresharkを使ったパケット解析の基本 - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2020/06/19/172659">blog.tech-monex.com</a></cite></p> <p>芦刈さんの、パケット解析ツールのWiresharkの紹介記事が8位です。</p> <p>ちなみに、上川は「Ethereal」時代の人です(笑)</p> <p>サーバー間通信でトラブった時、両端の機器でtcpdumpを取って、Etherealでパケットを分析したりよくしました。</p> <p>TCP層がIP層をカプセル化している様子とかが、非常によくわかって面白かった思い出があります。</p> <h5 id="第9位">第9位</h5> <p>Kotlin Multiplatform Mobile(KMM)によるロジックの共通化と、Reduxベースによるマルチプラットフォームアーキテクチャ</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2023%2F02%2F10%2F000000" title="Kotlin Multiplatform Mobile(KMM)によるロジックの共通化と、Reduxベースによるマルチプラットフォームアーキテクチャ - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2023/02/10/000000">blog.tech-monex.com</a></cite></p> <p>佐藤さんの記事が3本目のランクイン。</p> <p>先月投稿されたホットな記事ですが、読みごたえがあって面白いので上位に食い込んできましたね。</p> <h5 id="第10位">第10位</h5> <p>【AWS ECS】nginxとTomcatのコンテナをFargateで動作させる環境をCloudFormationで構築する</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.tech-monex.com%2Fentry%2F2020%2F07%2F17%2F191115" title="【AWS ECS】nginxとTomcatのコンテナをFargateで動作させる環境をCloudFormationで構築する - MONEX ENGINEER BLOG │マネックス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.tech-monex.com/entry/2020/07/17/191115">blog.tech-monex.com</a></cite></p> <p>Fargate関連の田代さんの記事が、再びランクインしました。</p> <p>やはりAWS系の利用実績紹介記事は強いです。</p> <h4 id="所感">所感</h4> <p>こうやってランキングを並べてみると、技術紹介系、特に実践例の紹介記事がやはり上位に入ってくるようです。</p> <p>エンジニアブログとしては、うれしいランキングですね。</p> <p>2023年度も、たくさんいい記事をアップして、みなさんのお役に立てるといいなと思います。</p> <p>そして、このようなブログ執筆者がいるマネックスに興味を持っていただいた方は、ぜひ一度ご気軽にご連絡ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.monexgroup.jp%2Frecruit%2Findex.html" title="マネックスグループ株式会社採用" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.monexgroup.jp/recruit/index.html">www.monexgroup.jp</a></cite></p> <p> </p> <div class="entry-writer"> <div class="image-round"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kingblogger/20200422/20200422215827.jpg" /></div> <span class="name">上川 和樹</span><span class="job">システム開発三部長 兼 システム開発三部 マネックス・ラボ長</span></div> kingblogger