<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Laure Retru-Chavastel on Medium]]></title>
        <description><![CDATA[Stories by Laure Retru-Chavastel on Medium]]></description>
        <link>https://medium.com/@laure_rc?source=rss-8fb677159f4f------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*BzLDWFKAfEBIgi4x3-XxpQ.jpeg</url>
            <title>Stories by Laure Retru-Chavastel on Medium</title>
            <link>https://medium.com/@laure_rc?source=rss-8fb677159f4f------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 24 May 2026 21:04:55 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@laure_rc/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[How we migrated our codebase from fp-ts to Effect]]></title>
            <link>https://medium.com/inato/how-we-migrated-our-codebase-from-fp-ts-to-effect-b71acd0c5640?source=rss-8fb677159f4f------2</link>
            <guid isPermaLink="false">https://medium.com/p/b71acd0c5640</guid>
            <category><![CDATA[effect]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[fpt]]></category>
            <category><![CDATA[migration]]></category>
            <dc:creator><![CDATA[Laure Retru-Chavastel]]></dc:creator>
            <pubDate>Mon, 03 Jun 2024 12:04:20 GMT</pubDate>
            <atom:updated>2024-06-06T07:33:08.228Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QVDZ2dMlPpWKHNrLqFVQaQ.jpeg" /></figure><h3>Summary</h3><p>At Inato, we migrated from fp-ts to Effect in early 2024. Given our substantial codebase (around 500k lines of typescript code), we needed a way of ensuring any new code could be written using Effect while allowing existing fp-ts code to coexist. We achieved this goal in just two months, dedicating around 10% of our time to it. In this article, you will find our detailed migration strategy, the helpers we developed (which you can find in this <a href="https://github.com/inato/effect-fpts-interop">repository</a>), and how we ensured a smooth transition of our codebase.</p><p><em>Dev.to version of this article is available </em><a href="https://dev.to/laurerc/how-we-migrated-our-codebase-from-fp-ts-to-effect-5bbk"><em>here</em></a><em>.</em></p><h3>Migrate to Effect, why?</h3><p>At Inato we were very motivated early on to adopt functional programming, so we started using fp-ts in our codebase at the beginning of 2020. If you want to know more about this, have a look at <a href="https://medium.com/inato/our-journey-to-functional-programing-36854a370de1">Our journey to functional programming</a>.</p><p>Let’s now get to the heart of the matter: at the beginning of this year, we officially decided to switch to Effect! Why?</p><ul><li>The main maintainer of fp-ts (gcanti 👋) <a href="https://dev.to/effect/a-bright-future-for-effect-455m">joined the Effect team</a> which presumably means less active development on the fp-ts side and positions Effect as a rather obvious next step.</li><li>Because of the learning curve associated with fp-ts and the lack of documentation. Developers who joined Inato in the last years have frequently mentioned it: learning fp-ts is not really straightforward. This is a strong point for Effect with top-notch documentation and a lot of resources for training.</li><li>For even more reasons, visit the <a href="https://effect.website/docs/other/fp-ts#comparison-table">Effect website which compares fp-ts and Effect</a>!</li></ul><p>Migrating our codebase to Effect is a great goal, but doing it turned out to be more challenging and required careful planning. We also wanted to limit the time spent on this project, so we agreed on a 2.5-month deadline. With all this in mind, we came up with the following strategy.</p><h3>The Migration Strategy</h3><p>First of, here’s a representation of our server-side codebase: we have use cases that represent our business actions, these use cases have multiple dependencies (services, repositories, etc. — we’ll refer to them as ports), and we also have runners that will execute our use cases:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oNS0Gvzno-zOUQlUS-6cng.png" /></figure><p>When we started the migration we had around 400 use cases and 80 ports and their adapters to migrate.</p><p>Our objective for this migration was clear: by the end of our 2.5-month window, any new use case or port will be written using Effect. To have a smooth transition that would allow us to have fp-ts and Effect code cohabitating, we came up with the following plan:</p><ol><li>Ensure our ports return ReaderTaskEither to facilitate the transition to Effect [*]</li><li>Create Effect proxies of our ports: only one implementation in fp-ts, but the ability to use an fp-ts “real” version and an Effect proxy version of each port</li><li>Start (re)writing use cases in Effect</li><li>Create fp-ts proxies of Effect use cases</li><li>Start (re)writing ports in Effect</li><li>Create fp-ts proxies of Effect ports: at this point, we would already have fulfilled our objective of writing new use cases and ports with Effect. But we wanted to go the extra mile to have the full flow covered!</li><li>Be able to run both Effect and fp-ts use cases</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4ncs2qIAmerwUB_S57CNkw.png" /></figure><p>[*] ReaderTaskEither (we will refer to it as RTE later on) was a prerequisite to facilitate the migration to Effect. Why? Conceptually, a ReaderTaskEither can be represented as follows:</p><pre>ReaderTaskEither&lt;R, E, A&gt;<br>= Reader&lt;R, TaskEither&lt;E, A&gt;&gt;<br>= (context: R) =&gt; () =&gt; Promise&lt;Either&lt;E, A&gt;&gt;</pre><p>If we look at the representation of an effect given on the <a href="https://effect.website/docs/guides/essentials/the-effect-type">official Effect website</a>, we can see that these are very similar concepts (which is something that we will leverage during our migration):</p><pre>Effect&lt;A, E, R&gt; ~ (context: Context&lt;R&gt;) =&gt; E | A</pre><h3>The Migration Process</h3><p>Let’s deep dive into the code now! Here are the steps we are going to follow:</p><ul><li><a href="#03c3">The program to migrate</a></li><li><a href="#3d23">Create Effect proxies of the ports</a></li><li><a href="#da8e">Rewrite a use case in Effect</a></li><li><a href="#13f4">Convert ports to Effect</a></li><li><a href="#016c">Use ManagedRuntime to run Effect usecases</a></li><li><a href="#0af5">Bonus: simplify fp-ts ↔ effect tag mapping management</a></li></ul><p>To illustrate our migration process, we will focus on an example program that is representative of how our codebase is organized.</p><p><em>Note: all the code and helpers that will be presented are available in 👉 </em><a href="https://github.com/inato/effect-fpts-interop"><em>this repository</em></a><em> 👈</em></p><h4>The program to migrate</h4><p>Let say that our domain model is composed of a simple Foo class:</p><pre>// domain.ts<br>export class Foo {<br>  constructor(readonly id: string) {}<br>  static make = (id = &quot;random-id&quot;) =&gt; new Foo(id);<br>}</pre><p>We define a repository port to get and store a Foo:</p><pre>// FooRepository.ts<br>export interface FooRepository {<br>  getById: (id: string) =&gt; RTE&lt;unknown, Error, Foo&gt;;<br>  store: (foo: Foo) =&gt; RTE&lt;unknown, Error, void&gt;;<br>}<br><br>export interface FooRepositoryAccess {<br>  fooRepository: FooRepository;<br>}<br>export declare const FooRepository: {<br>  getById: (id: string) =&gt; RTE&lt;FooRepositoryAccess, Error, Foo&gt;;<br>  store: (foo: Foo) =&gt; RTE&lt;FooRepositoryAccess, Error, void&gt;;<br>};<br>export declare const makeFooRepository: () =&gt; Promise&lt;FooRepository&gt;;</pre><p>Note:</p><ul><li>We follow the <a href="https://degoes.net/articles/zio-environment#the-module-pattern">module pattern</a> when defining the FooRepositoryAccess interface to enable context aggregation when composing multiple ReaderTaskEither:</li></ul><pre>declare const a: RTE&lt;{ serviceA: ServiceA },never,void&gt;<br>declare const b: RTE&lt;{ serviceB: ServiceB },never,void&gt;<br>const ab: RTE&lt;{ serviceA: ServiceA; serviceB: ServiceB },never,void&gt; <br>    = rte.flatMap(a,() =&gt; b)</pre><ul><li>We define a <a href="https://stefan-bauer.online/blog/posts/writing-better-type-script#know-and-use-the-companion-object-pattern">companion object</a> FooRepository that exposes the same methods as the repository itself, except that they each require a context with FooRepositoryAccess. This makes for more concise code later on:</li></ul><pre>const theLongWay: RTE&lt;FooRepositoryAccess, Error, Foo&gt; = pipe(<br> rte.ask&lt;FooRepositoryAccess&gt;(),<br> rte.flatMap(({ fooRepository }) =&gt; fooRepository.getById(&#39;id&#39;))<br>);<br><br>const theEasyWay: RTE&lt;FooRepositoryAccess, Error, Foo&gt; <br> = FooRepository.getById(&#39;id&#39;)</pre><p>We also define a service port to transform a Foo:</p><pre>// TransformFooService.ts<br>export interface TransformFooService {<br>  transform: (foo: Foo) =&gt; RTE&lt;unknown, Error, Foo&gt;;<br>}<br><br>export interface TransformFooServiceAccess {<br>  transformFooService: TransformFooService;<br>}<br><br>export declare const TransformFooService: {<br>  transform: (foo: Foo) =&gt; RTE&lt;TransformFooServiceAccess, Error, Foo&gt;;<br>};<br><br>declare const makeTransformFooService: () =&gt; Promise&lt;TransformFooService&gt;;</pre><p>Next we can write two use cases: one to create a new Foo , and another one to transform a Foo:</p><pre>// usecases.ts<br>export const createFooUseCase = (id:string) =&gt; <br> pipe(<br>   rte.of(Foo.make(id)),<br>   rte.tap(FooRepository.store)<br> );<br><br>export const transformFooUseCase = (id: string) =&gt;<br>  pipe(<br>    FooRepository.getById(id),<br>    rte.flatMap(TransformFooService.transform),<br>    rte.flatMap(FooRepository.store)<br>  );</pre><p>Finally, we can write our main that will create all the port adapters and invoke our use cases:</p><pre>// index.ts<br>const main = async () =&gt; {<br>  const fooRepository = await makeFooRepository();<br>  const transformFooService = await makeTransformFooService();<br>  await createFooUseCase(&quot;my-foo-id&quot;)({<br>    transformFooService,<br>    fooRepository,<br>  })();<br>  await transformFooUseCase(&quot;my-foo-id&quot;)({<br>    transformFooService,<br>    fooRepository,<br>  })();<br>};<br>main();</pre><h4>Create Effect proxies of the ports</h4><p>This step consists in generating new companion objects FooRepository and TransformFooService for our ports that are exposing an Effect version of the member methods.</p><p>First we rename the companion objects, adding a Fpts suffix:</p><pre>// FooRepository.ts<br>e̶x̶p̶o̶r̶t̶ ̶d̶e̶c̶l̶a̶r̶e̶ ̶c̶o̶n̶s̶t̶ ̶F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶:̶ ̶{̶<br>export declare const FooRepositoryFpts: {<br>  getById: (id: string) =&gt; RTE&lt;FooRepositoryAccess, Error, Foo&gt;;<br>  store: (foo: Foo) =&gt; RTE&lt;FooRepositoryAccess, Error, void&gt;;<br>};<br><br>// TransformFooService.ts<br>e̶x̶p̶o̶r̶t̶ ̶d̶e̶c̶l̶a̶r̶e̶ ̶c̶o̶n̶s̶t̶ ̶T̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶:̶ ̶{̶<br>export declare const TransformFooServiceFpts: {<br>  transform: (foo: Foo) =&gt; RTE&lt;TransformFooServiceAccess, Error, Foo&gt;;<br>};<br><br>// usecases.ts<br>export const createFooUseCase = (id:string) =&gt; <br> pipe(<br>   rte.of(Foo.make(id)),<br>   r̶t̶e̶.̶t̶a̶p̶(̶F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶.̶s̶t̶o̶r̶e̶)̶<br>   rte.tap(FooRepositoryFpts.store)<br> );<br><br>export const transformFooUseCase = (id: string) =&gt;<br>  pipe(<br>    F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶.̶g̶e̶t̶B̶y̶I̶d̶(̶i̶d̶)̶,̶<br>    r̶t̶e̶.̶f̶l̶a̶t̶M̶a̶p̶(̶T̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶.̶t̶r̶a̶n̶s̶f̶o̶r̶m̶)̶,̶<br>    r̶t̶e̶.̶f̶l̶a̶t̶M̶a̶p̶(̶F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶.̶s̶t̶o̶r̶e̶)̶<br>    FooRepositoryFpts.getById(id),<br>    rte.flatMap(TransformFooServiceFpts.transform),<br>    rte.flatMap(FooRepositoryFpts.store)<br>  );</pre><p>Then we use the portToEffect helper function to generate the Effect companion objects from the previous companion objects:</p><pre>// FooRepository.ts<br>export const FooRepositoryTag = Context.GenericTag&lt;FooRepository&gt;(<br> &quot;FooRepository&quot;<br>);<br>export const FooRepository = portToEffect(FooRepositoryFpts, {<br>  fooRepository: FooRepositoryTag,<br>}); // { getById: (id: string) =&gt; Effect&lt;Foo, Error, FooRepository&gt; ... }<br><br>// TransformFooService.ts<br>export const TransformFooServiceTag = Context.GenericTag&lt;TransformFooService&gt;(<br>  &quot;TransformFooService&quot;<br>);<br>export const TransformFooService = portToEffect(TransformFooServiceFpts, {<br>  transformFooService: TransformFooServiceTag,<br>}); // { transform: (foo: Foo) =&gt; Effect&lt;Foo, Error, TransformFooService&gt; }</pre><h4>Rewrite a use case in Effect</h4><p>At this point we can start using our newly generated Effect companion objects to rewrite the transformFooUseCase use case in Effect. Note that we voluntarily leave the createFooUseCase use case as is to simulate a migration that is ongoing, as opposed to a “big-bang” migration where we would convert all of our use cases to Effect in one go (much harder and riskier).</p><pre>// usecases.ts<br>export const transformFooUseCase = (id: string) =&gt;<br>  pipe(<br>    F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶F̶p̶t̶s̶.̶g̶e̶t̶B̶y̶I̶d̶(̶i̶d̶)̶,̶<br>    r̶t̶e̶.̶f̶l̶a̶t̶M̶a̶p̶(̶T̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶F̶p̶t̶s̶.̶t̶r̶a̶n̶s̶f̶o̶r̶m̶)̶,̶<br>    r̶t̶e̶.̶f̶l̶a̶t̶M̶a̶p̶(̶F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶F̶p̶t̶s̶.̶s̶t̶o̶r̶e̶)̶<br>    FooRepository.getById(id),<br>    Effect.flatMap(TransformFooService.transform),<br>    Effect.flatMap(FooRepository.store)<br>  ); // Effect&lt;void, Error, TransformFooService | FooRepository&gt;</pre><p>Since we don’t want to impact our main program yet, we must maintain an fp-ts version of this use case, for backward compatibility. We can generate it from the Effect version thanks to the functionToFpts helper function:</p><pre>// usecases.ts<br>export const transformFooUseCaseFpts = functionToFpts(transformFooUseCase, {<br>  fooRepository: FooRepositoryTag,<br>  transformFooService: TransformFooServiceTag,<br>}); // RTE&lt;TransformFooServiceAccess &amp; FooRepositoryAccess, Error, void&gt;<br><br>// index.ts<br>const main = async () =&gt; {<br>  const fooRepository = await makeFooRepository();<br>  const transformFooService = await makeTransformFooService();<br>  await createFooUseCase(&quot;my-foo-id&quot;)({<br>   transformFooService,<br>    fooRepository,<br>  })();<br>  a̶w̶a̶i̶t̶ ̶t̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶U̶s̶e̶C̶a̶s̶e̶(̶&quot;̶m̶y̶-̶f̶o̶o̶-̶i̶d̶&quot;̶)̶(̶{̶<br>  await transformFooUseCaseFpts(&quot;my-foo-id&quot;)({<br>    transformFooService,<br>    fooRepository,<br>  })();<br>};<br>main();</pre><h4>Convert ports to Effect</h4><p>Next we convert our FooRepository port to Effect directly:</p><pre>// FooRepository.ts<br>export interface FooRepository {<br>  g̶e̶t̶B̶y̶I̶d̶:̶ ̶(̶i̶d̶:̶ ̶s̶t̶r̶i̶n̶g̶)̶ ̶=̶&gt;̶ ̶R̶T̶E̶&lt;̶u̶n̶k̶n̶o̶w̶n̶,̶ ̶E̶r̶r̶o̶r̶,̶ ̶F̶o̶o̶&gt;̶;̶<br>  s̶t̶o̶r̶e̶:̶ ̶(̶f̶o̶o̶:̶ ̶F̶o̶o̶)̶ ̶=̶&gt;̶ ̶R̶T̶E̶&lt;̶u̶n̶k̶n̶o̶w̶n̶,̶ ̶E̶r̶r̶o̶r̶,̶ ̶v̶o̶i̶d̶&gt;̶;̶<br>  getById: (id: string) =&gt; Effect.Effect&lt;Foo, Error&gt;;<br>  store: (foo: Foo) =&gt; Effect.Effect&lt;void, Error&gt;;<br>}</pre><p>We can now generate the Effect companion object using Effect.serviceFunctions:</p><pre>// FooRepository.ts<br>e̶x̶p̶o̶r̶t̶ ̶c̶o̶n̶s̶t̶ ̶F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶ ̶=̶ ̶p̶o̶r̶t̶T̶o̶E̶f̶f̶e̶c̶t̶(̶F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶F̶p̶t̶s̶,̶ ̶{̶<br>̶ ̶ ̶f̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶:̶ ̶F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶T̶a̶g̶,̶<br>̶}̶)̶;̶<br>export const FooRepository = Effect.serviceFunctions(FooRepositoryTag);</pre><p>Finally, for backward compatibility, we must maintain the fp-ts companion object. We can generate it using the portToFpts helper function:</p><pre>// FooRepository.ts<br>e̶x̶p̶o̶r̶t̶ ̶d̶e̶c̶l̶a̶r̶e̶ ̶c̶o̶n̶s̶t̶ ̶F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶F̶p̶t̶s̶:̶ ̶{̶<br>̶ ̶ ̶g̶e̶t̶B̶y̶I̶d̶:̶ ̶(̶i̶d̶:̶ ̶s̶t̶r̶i̶n̶g̶)̶ ̶=̶&gt;̶ ̶R̶T̶E̶&lt;̶F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶A̶c̶c̶e̶s̶s̶,̶ ̶E̶r̶r̶o̶r̶,̶ ̶F̶o̶o̶&gt;̶;̶<br>̶ ̶ ̶s̶t̶o̶r̶e̶:̶ ̶(̶f̶o̶o̶:̶ ̶F̶o̶o̶)̶ ̶=̶&gt;̶ ̶R̶T̶E̶&lt;̶F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶A̶c̶c̶e̶s̶s̶,̶ ̶E̶r̶r̶o̶r̶,̶ ̶v̶o̶i̶d̶&gt;̶;̶<br>̶}̶;̶<br><br>export const FooRepositoryFpts = portToFpts(FooRepository, {<br>  fooRepository: FooRepositoryTag,<br>}); // { getById: (id: string) =&gt; RTE&lt;FooRepositoryAccess, Error, Foo&gt;; ... }</pre><p>We do the same for the TransformFooService port:</p><pre>// TransformFooService.ts<br>export interface TransformFooService {<br>  t̶r̶a̶n̶s̶f̶o̶r̶m̶:̶ ̶(̶f̶o̶o̶:̶ ̶F̶o̶o̶)̶ ̶=̶&gt;̶ ̶R̶T̶E̶&lt;̶u̶n̶k̶n̶o̶w̶n̶,̶ ̶E̶r̶r̶o̶r̶,̶ ̶F̶o̶o̶&gt;̶;̶<br>  transform: (foo: Foo) =&gt; Effect&lt;Foo, Error&gt;;<br>}<br><br>e̶x̶p̶o̶r̶t̶ ̶c̶o̶n̶s̶t̶ ̶T̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶ ̶=̶ ̶p̶o̶r̶t̶T̶o̶E̶f̶f̶e̶c̶t̶(̶T̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶F̶p̶t̶s̶,̶ ̶{̶<br>̶ ̶ ̶t̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶:̶ ̶T̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶T̶a̶g̶,̶<br>̶}̶)̶;̶<br>export const TransformFooService = Effect.serviceFunctions(<br> TransformFooServiceTag<br>);<br><br>e̶x̶p̶o̶r̶t̶ ̶d̶e̶c̶l̶a̶r̶e̶ ̶c̶o̶n̶s̶t̶ ̶T̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶F̶p̶t̶s̶:̶ ̶{̶<br>̶ ̶ ̶t̶r̶a̶n̶s̶f̶o̶r̶m̶:̶ ̶(̶f̶o̶o̶:̶ ̶F̶o̶o̶)̶ ̶=̶&gt;̶ ̶R̶T̶E̶&lt;̶T̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶A̶c̶c̶e̶s̶s̶,̶ ̶E̶r̶r̶o̶r̶,̶ ̶F̶o̶o̶&gt;̶;̶<br>̶}̶;̶<br>export const TransformFooServiceFpts = portToFpts(TransformFooService, {<br>  fooRepository: FooRepositoryTag,<br>}); // { transform: (foo: Foo) =&gt; RTE&lt;unknown, Error, Foo&gt;; }</pre><p>Note that we have not changed our main in this step and it can still be run without a problem.</p><h4>Use ManagedRuntime to run Effect usecases</h4><p>In order to run the transformFooUseCase as an Effect, we must be able to provide our ports via Layers:</p><pre>// FooRepository.ts<br>e̶x̶p̶o̶r̶t̶ ̶d̶e̶c̶l̶a̶r̶e̶ ̶c̶o̶n̶s̶t̶ ̶m̶a̶k̶e̶F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶:̶ ̶(̶)̶ ̶=̶&gt;̶ ̶P̶r̶o̶m̶i̶s̶e̶&lt;̶F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶&gt;̶;̶<br>export declare const FooRepositoryLive: Layer.Layer&lt;FooRepository&gt;;<br><br>// TransformFooService.ts<br>d̶e̶c̶l̶a̶r̶e̶ ̶c̶o̶n̶s̶t̶ ̶m̶a̶k̶e̶T̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶:̶ ̶(̶)̶ ̶=̶&gt;̶ ̶P̶r̶o̶m̶i̶s̶e̶&lt;̶T̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶&gt;̶;̶<br>declare const TransformFooServiceLive: Layer.Layer&lt;TransformFooService&gt;;</pre><p>Next we can create a ManagedRuntime and extract all the ports from the runtime context using the contextToFpts helper:</p><pre>// index.ts<br>const main = async () =&gt; {<br>  c̶o̶n̶s̶t̶ ̶f̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶ ̶=̶ ̶a̶w̶a̶i̶t̶ ̶m̶a̶k̶e̶F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶(̶)̶;̶<br>  c̶o̶n̶s̶t̶ ̶t̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶ ̶=̶ ̶a̶w̶a̶i̶t̶ ̶m̶a̶k̶e̶T̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶(̶)̶;̶<br>  const runtime = ManagedRuntime.make(<br>    Layer.mergeAll(FooRepositoryLive, TransformFooServiceLive)<br>  );<br>  const { context } = await runtime.runtime();<br>  const { fooRepository, transformFooService } = contextToFpts(context, {<br>    fooRepository: FooRepositoryTag,<br>    transformFooService: TransformFooServiceTag,<br>  });<br>  await createFooUseCase(&quot;my-foo-id&quot;)({<br>   transformFooService,<br>    fooRepository,<br>  })();<br>  await transformFooUseCaseFpts(&quot;my-foo-id&quot;)({<br>    transformFooService,<br>    fooRepository,<br>  })();<br>};<br>main();</pre><p>Finally, we can use the runtime to run the Effect transformFooUseCase:</p><pre>// index.ts<br>const main = async () =&gt; {<br>  const runtime = ManagedRuntime.make(<br>    Layer.mergeAll(FooRepositoryLive, TransformFooServiceLive)<br>  );<br>  const { context } = await runtime.runtime();<br>  const { fooRepository, transformFooService } = contextToFpts(context, {<br>    fooRepository: FooRepositoryTag,<br>    transformFooService: TransformFooServiceTag,<br>  });<br>  await createFooUseCase(&quot;my-foo-id&quot;)({<br>   transformFooService,<br>    fooRepository,<br>  })();<br>  a̶w̶a̶i̶t̶ ̶t̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶U̶s̶e̶C̶a̶s̶e̶F̶p̶t̶s̶(̶&quot;̶m̶y̶-̶f̶o̶o̶-̶i̶d̶&quot;̶)̶(̶{̶<br>   ̶ ̶t̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶,̶<br>   ̶ ̶f̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶,̶<br>  }̶)̶(̶)̶;̶<br>  await runtime.runPromise(transformFooUseCase(&quot;my-foo-id&quot;));<br>};<br>main();</pre><p>Note that, once again, we left the createFooUseCase use case as is to show that we can be in a hybrid state where only part of the use cases have been migrated to Effect.</p><h4>Bonus: simplify fp-ts ↔ effect tag mapping management</h4><p>All of the helpers we have used throughout this migration require a mapping object to go from the key name of the fp-ts port Access interface (eg transformFooService of TransformFooServiceAccess) to the Tag of the corresponding Effect port. For example:</p><pre>contextToFpts(context, {<br>  fooRepository: FooRepositoryTag,<br>  transformFooService: TransformFooServiceTag,<br>});</pre><p>This mapping is essential for all the helpers to work correctly. It is not ideal to have to craft them like that all the time. To help us with that, we introduce:</p><pre>const FptsConvertibleId = Symbol();<br>interface FptsConvertible&lt;T extends string&gt; { <br>  [FptsConvertibleId]: T;<br>}</pre><p>We can now embed this conversion information at the type level of our ports:</p><pre>// FooRepository.ts<br>export interface FooRepository extends FptsConvertible&lt;&quot;fooRepository&quot;&gt; {<br>  getById: (id: string) =&gt; Effect.Effect&lt;Foo, Error&gt;;<br>  store: (foo: Foo) =&gt; Effect.Effect&lt;void, Error&gt;;<br>}<br><br>// TransformFooService.ts<br>export interface TransformFooService <br>  extends FptsConvertible&lt;&quot;transformFooService&quot;&gt; {<br>  transform: (foo: Foo) =&gt; Effect&lt;Foo, Error&gt;;<br>}</pre><p>The first thing we can do with this is to simplify the definition of Access interfaces using a type helper FptsAccess:</p><pre>// FooRepository.ts<br>export interface FooRepositoryAccess extends FptsAccess&lt;FooRepository&gt; {}<br><br>// TransformFooService.ts<br>export interface TransformFooServiceAccess <br>  extends FptsAccess&lt;TransformFooService&gt; {}</pre><p>And we can also define smaller atomic mapping objects using a new helper getFptsMapping:</p><pre>// FooRepository.ts<br>const FooRepositoryFptsMapping = getFptsMapping(<br>  FooRepositoryTag,<br>  &quot;fooRepository&quot;<br>); // { fooRepository: FooRepositoryTag }<br><br>// TransformFooService.ts<br>const TransformFooServiceFptsMapping = getFptsMapping(<br>  TransformFooServiceTag,<br>  &quot;transformFooService&quot;<br>); // { transformFooService: TransformFooServiceTag }</pre><p>Note: It looks like we are once again typing the key &quot;fooRepository&quot; or &quot;transformFooService&quot; but in fact, the function getFptsMapping is type-safe so that given FooRepositoryTag as first argument, only the string &quot;fooRepository&quot; is valid as second argument. So your code editor will autocomplete it for you. Moreover, the compiler will break if you change the definition in the FptsConvertible so it is not really an additional burden.</p><p>We can now combine these two mapping objects when calling contextToFpts or any other helper:</p><pre>contextToFpts(context, {<br>  f̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶:̶ ̶F̶o̶o̶R̶e̶p̶o̶s̶i̶t̶o̶r̶y̶T̶a̶g̶,̶<br>  t̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶:̶ ̶T̶r̶a̶n̶s̶f̶o̶r̶m̶F̶o̶o̶S̶e̶r̶v̶i̶c̶e̶T̶a̶g̶,̶<br>  ...FooRepositoryFptsMapping,<br>  ...TransformFooServiceFptsMapping,<br>});</pre><h3>Conclusion</h3><p>Our objective of being able to write any new use case or port using Effect was accomplished in 2 months (working around 10% of our time on it)!</p><p>Teamwork was definitely a big part of this success: first, we have to mention <a href="https://medium.com/u/c67fddd7ca1c">Stephane Ledorze</a> as he migrated all our repositories single-handedly and gave us great advice on how to define our migration strategy. We handled the rest with the whole team during dedicated “tech sessions” that we do every Wednesday afternoon at Inato: during those sessions, we stop delivering features to be able to focus on purely tech subjects, which was a great occasion to migrate the many ports we had to handle and onboard the team on Effect.</p><p>As we’re writing this article, we have around 150 full Effect use cases. The rest of the existing use cases will be migrated on the go whenever we need to update them!</p><p>We’re already seeing great improvements: for example, implementing rate limiting with just a few lines of code with Effect, whereas we needed a big amount of code to do it with fp-ts. We’re eager to leverage even more the Effect ecosystem now that we have officially migrated to it!</p><p>We hope this article motivated you to take the leap from fp-ts to Effect, don’t hesitate to comment if you have any questions or comments!</p><p><em>This article was written by </em><a href="https://medium.com/u/32aa24b2222c"><em>Jeremie Dayan</em></a><em> and </em><a href="https://medium.com/u/8fb677159f4f"><em>Laure Retru-Chavastel</em></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b71acd0c5640" width="1" height="1" alt=""><hr><p><a href="https://medium.com/inato/how-we-migrated-our-codebase-from-fp-ts-to-effect-b71acd0c5640">How we migrated our codebase from fp-ts to Effect</a> was originally published in <a href="https://medium.com/inato">inato</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Improve your React app performance using React Profiler]]></title>
            <link>https://medium.com/inato/prevent-re-renders-in-your-react-app-using-react-profiler-93c492110e30?source=rss-8fb677159f4f------2</link>
            <guid isPermaLink="false">https://medium.com/p/93c492110e30</guid>
            <category><![CDATA[rerender]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[profiler]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Laure Retru-Chavastel]]></dc:creator>
            <pubDate>Mon, 02 May 2022 13:20:54 GMT</pubDate>
            <atom:updated>2022-05-02T17:24:45.399Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ynISD6iYRXsbOkjy" /><figcaption>Photo by <a href="https://unsplash.com/@chrisliverani?utm_source=medium&amp;utm_medium=referral">Chris Liverani</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Recently, I was trying to improve the performance of a page in our React app, and I quickly suspected unnecessary re-renders to be the main issue there. After some investigations, I stumbled upon the React DevTools profiler plugin:</p><blockquote>This plugin uses React’s <a href="https://github.com/reactjs/rfcs/pull/51">experimental Profiler API</a> to collect timing information about each component that’s rendered in order to identify performance bottlenecks in React applications. (<a href="https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html">source</a>)</blockquote><p>More particularly, I discovered a kind of hidden option of this plugin: being able to know why a component rendered. I found this feature so useful that I decided to create this article to share how to use it!</p><h3>Prerequisites:</h3><ul><li>Use React ≥ 16.5 in DEV mode</li><li>Have React DevTools as an extension for your browser (<a href="https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en">Chrome</a> or <a href="https://addons.mozilla.org/en-US/firefox/addon/react-devtools/">Firefox</a>)</li></ul><h3>Improve a basic app with React Profiler</h3><p>First of all, here is how to enable this option in your DevTools:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*rLOAjb3ib4BopsDQM4lgqw.gif" /></figure><p>Now, let’s take the example of <a href="https://codesandbox.io/s/react-profiler-rerenders-vo1986?file=/src/App.tsx">this simple application</a> where you can see your goals of the day and set them as done. Let’s use React profiler to generate a profile and check what happens in our application after we click on the “Set as done” button.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*k7ROvT4NZ7huVa5ewmfaPA.gif" /></figure><blockquote>“The color of a bar indicates how long the component (and its children) took to render in the selected commit. Yellow components took more time, blue components took less time, and gray components did not render at all during this commit.” (<a href="https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html#flame-chart">source</a>)</blockquote><p>We can see that all the “Goal” sub-components were re-rendered. Ideally, as we only updated the Goal “Read 30 min” we would expect that only this Goal component would be re-rendered, not the other Goal components.</p><p>How can we fix this? Now is the time to take advantage of the option we selected earlier to know why a component rendered! Let’s hover the first Goal component in the profiling data:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HnD4aTcFB19Q92dWLeZsOA.png" /></figure><p>So the cause is that the property setAsDone has changed. Let’s fix this quickly:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eICdp6zWW-mdDjwqyuATlg.png" /></figure><p>Thanks to that change, the method setAsDone won’t change at each render of the Goals component. Unfortunately, that won’t be enough here to prevent the re-render of the other Goal components. Here is what the profiling data tell us now:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ztu9JlDm7_Q_Uwcv7Hkbrg.png" /></figure><p>So Goal components re-rendered because Goals component re-rendered. And Goals component re-rendered because “Hook 1 changed”, which is not very explicit right?</p><p>To know what this “Hook 1” is, let’s switch to the “Components” tab and the DevTools will smartly show you the right component:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OoMWbZnYI7rLBa09C2IAfA.png" /></figure><p>We can now see that &quot;Hook 1 changed” actually means &quot;the state goalsReached changed” which is indeed the case as we added the goal “Read 30 min” in this state.</p><p>In this specific case, we could use React.memo to fix our issue (it might not be the best solution in every case of course, but let’s keep things simple for this example).</p><blockquote>“If your component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.” (<a href="https://reactjs.org/docs/react-api.html#reactmemo">source</a>)</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LsDxMwOzbH7wqkx_Jlw28g.png" /></figure><p>Let’s profile our app one more time:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*N-eybv9ie8iH_y_JZpbxjw.png" /></figure><p>We can now see that only the right Goal component was re-rendered, while the others were not 🥳</p><p>That should help you in some of your investigations to improve the performance of your React app! Feel free to ask your questions or share your experience and tips with React Profiler in the comments!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=93c492110e30" width="1" height="1" alt=""><hr><p><a href="https://medium.com/inato/prevent-re-renders-in-your-react-app-using-react-profiler-93c492110e30">Improve your React app performance using React Profiler</a> was originally published in <a href="https://medium.com/inato">inato</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to use Terraform to schedule backups for your Google Cloud SQL database]]></title>
            <link>https://medium.com/inato/how-to-use-terraform-to-schedule-backups-for-your-google-cloud-sql-database-8688da8180e1?source=rss-8fb677159f4f------2</link>
            <guid isPermaLink="false">https://medium.com/p/8688da8180e1</guid>
            <category><![CDATA[terraform]]></category>
            <category><![CDATA[cloud-functions]]></category>
            <category><![CDATA[backup]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[sql-database]]></category>
            <dc:creator><![CDATA[Laure Retru-Chavastel]]></dc:creator>
            <pubDate>Tue, 10 Mar 2020 16:29:42 GMT</pubDate>
            <atom:updated>2020-03-10T17:04:58.935Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*WWPxZLAHJhBsC21o" /><figcaption>Photo by <a href="https://unsplash.com/@matthewwaring?utm_source=medium&amp;utm_medium=referral">Matthew Waring</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>You may be thinking “but Google SQL database instances already provide automated and on-demand backups, why would you need to use terraform to deal with it?”. Well, the issue is that if your database instance is deleted, for instance after a manual mistake or a Google Cloud SQL outage, so are your backups!</p><p>This is why we decided to save those backups in a google storage bucket, and you will soon discover that this process is really easy to do with Terraform.</p><p>Prerequisites :</p><ul><li>Have a project in Google Cloud Platform (GCP)</li><li>Have a Cloud SQL database defined in GCP</li><li>Install Terraform CLI on your computer (see documentation <a href="https://learn.hashicorp.com/terraform/getting-started/install.html">here</a>, or simply run brew install terraform if you are a mac user)</li><li>Install <a href="https://cloud.google.com/sdk/install">gcloud</a> CLI</li></ul><p>Here are the steps we are going to follow in this article :</p><ol><li><a href="https://medium.com/p/8688da8180e1#0ef4">Setup the terraform config for your Google Cloud project</a></li><li><a href="https://medium.com/p/8688da8180e1#2f17">Import your database into terraform</a></li><li><a href="https://medium.com/p/8688da8180e1#7c4e">Create a bucket to store your backups</a></li><li><a href="https://medium.com/p/8688da8180e1#6ec3">Create a cloud function to store a compressed dump of your database in this bucket</a></li><li><a href="https://medium.com/p/8688da8180e1#f524">Create a cloud scheduler to trigger a daily backup of your database</a></li></ol><h3>1. Setup the terraform config for your Google Cloud project</h3><ul><li>Run gcloud init and gcloud auth application-default login to allow terraform to benefit from your access rights when you run terraform commands locally</li><li>From your console, create a new bucket (its name has to be unique among all the buckets that exist in GCP so we suffix it with the project id for this purpose) :</li></ul><pre>PROJECT_ID=&quot;your-project-id&quot;<br>BUCKET_NAME=&quot;gs://terraform-state-$PROJECT_ID&quot;<br>gsutil mb -p $PROJECT_ID $BUCKET_NAME<br>gsutil versioning set on $BUCKET_NAME</pre><ul><li>Go in GCP and check that a bucket terraform-state-your-project-id has been created</li><li>In your project repository, create a folder terraform with the following architecture :</li></ul><pre>terraform/<br>  production/ // I named it after the name of a specific environment<br>              // but you can name it as you like</pre><ul><li>Create a file production/main.tf :</li></ul><pre>terraform {<br> backend &quot;gcs&quot; {<br>  bucket = &quot;terraform-state-your-project-id&quot;<br>  prefix = &quot;terraform/state&quot;<br> }<br>}</pre><pre>provider &quot;google&quot; {<br> region = &quot;europe-west1&quot;<br> zone = &quot;europe-west1-d&quot;<br>}</pre><p>You may be wondering “but what is this terraform state”? This state is a sort of screenshot of your resources configuration that you are about to link with Terraform. It will use this state to make changes to your infrastructure.</p><p>An interesting advantage of storing this state in a bucket is that everyone will share the same configuration. Moreover, if you are editing the state, Terraform will lock it so that no one else would be able to edit it at the same time.</p><ul><li>Navigate in your console into your production terraform folder and run terraform init</li><li>Go in GCP and check that the bucket has a file terraform.tfstate which contains the terraform state</li></ul><h3>2. Import your database into Terraform</h3><p>Now that Terraform is ready, we can add your database in its state.</p><ul><li>Create a production/variables.tf file :</li></ul><pre>variable &quot;project_id&quot; {}</pre><ul><li>Create a production/terraform.tfvars file :</li></ul><pre>project_id = &quot;your-project-id&quot;</pre><ul><li>In production/main.tf, add the database resource config :</li></ul><pre>resource &quot;google_sql_database_instance&quot; &quot;your-database&quot; {<br>  project          = var.project_id<br>  name             = &quot;your-database&quot;<br>  database_version = &quot;POSTGRES_9_6&quot;<br>  region           = &quot;europe-west1&quot;</pre><pre>  settings {<br>    tier = &quot;db-custom-16-106496&quot;<br>  }<br>}</pre><p>Here, I specified all the required fields of google_sql_database_instance (see <a href="https://www.terraform.io/docs/providers/google/r/sql_database_instance.html">documentation</a>) but also the project field, as it is a good practice to be sure that your resource is going to be linked to the right project.</p><p>If you don’t know if these values are adapted to your database, don’t worry, Terraform won’t change the properties of your database without your consent!</p><ul><li>In your terminal, navigate to the folder containing your files and import your database config into Terraform:</li></ul><pre>terraform import google_sql_database_instance.your-database projects/your-project-id/instances/your-sql-database-instance-name</pre><p>This command will tell Terraform “the resource called google_sql_database_instance.your-database that I have defined in main.tf is linked to the real resource that you can find here: projects/your-project-id/instances/your-sql-database-instance-name”.</p><ul><li>Let’s run terraform plan to check if there are any differences between the configuration we defined and your real database configuration</li><li>If there are, edit your configuration in production/main.tf. For instance, if the region of your database is in fact “us-east1” instead of “europe-west1”, change the value of the region field in your configuration.</li><li>Repeat the two previous steps until the command terraform plan displays: “No changes. Infrastructure is up-to-date.”</li></ul><h3>3. Create a GCP bucket to store your backups</h3><ul><li>In the terraform folder, create a new folder modules/db-backups , so your architecture should look like this :</li></ul><pre>terraform/<br>  modules/<br>    db-backups/<br>  production/<br>    main.tf<br>    variables.tf<br>    terraform.tfvars</pre><ul><li>In this db-backups folder, create a file main.tf :</li></ul><pre>resource &quot;google_storage_bucket&quot; &quot;db-backups&quot; {<br>  project       = var.project_id<br>  name          = &quot;db-backups-${var.project_id}&quot;<br>  location      = &quot;EU&quot;<br>  storage_class = &quot;MULTI_REGIONAL&quot;<br>}</pre><ul><li>Again, we will need a file db-backups/variables.tf :</li></ul><pre>variable &quot;project_id&quot; {}</pre><ul><li>And a file db-backups/terraform.tfvars :</li></ul><pre>project_id = &quot;your-project-id&quot;</pre><ul><li>Our module is now ready to be used! Go back in the production/main.tf and add your module :</li></ul><pre>module &quot;db_backups&quot; {<br>  source                = &quot;../modules/db-backups&quot;<br>  project_id            = var.project_id<br>}</pre><ul><li>In your terminal, navigate to the production folder and run terraform apply (it first does the same as terraform plan and then ask you if you want to apply your changes). You should see that Terraform wants to add a new bucket. Check that nothing else is going to be done, and if so, write “yes” and apply your changes.</li><li>Go in GCP: you should see a new bucket db-backups in your project, it is empty for now but we are soon going to fill it!</li></ul><h3>4. Create a cloud function to store a compressed dump of your database in this bucket</h3><p><strong>4.1 Create a zip file containing the code of your cloud function</strong></p><ul><li>First of all, create a new folder inside modules/db-backups/ named backupCloudFunction</li><li>In there, create a new file index.js :</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1e9635dd888079d1e5b565b776f9433e/href">https://medium.com/media/1e9635dd888079d1e5b565b776f9433e/href</a></iframe><ul><li>And also, a package.json file:</li></ul><pre>{<br>    &quot;name&quot;: &quot;export-database&quot;,<br>    &quot;version&quot;: &quot;0.0.1&quot;,<br>    &quot;dependencies&quot;: {<br>      &quot;googleapis&quot;: &quot;^39.2.0&quot;,<br>      &quot;google-auth-library&quot;: &quot;3.1.2&quot;<br>    }<br>}</pre><ul><li>Then, let’s create a zip with Terraform from those two files that we will call backup.zip:</li></ul><pre>data &quot;archive_file&quot; &quot;backupZipFile&quot; {<br>  type        = &quot;zip&quot;<br>  output_path = &quot;${path.module}/backupCloudFunction/backup.zip&quot;</pre><pre>source {<br>    content  = &quot;${file(&quot;${path.module}/backupCloudFunction/index.js&quot;)}&quot;<br>    filename = &quot;index.js&quot;<br>  }</pre><pre>source {<br>    content  = &quot;${file(&quot;${path.module}/backupCloudFunction/package.json&quot;)}&quot;<br>    filename = &quot;package.json&quot;<br>  }<br>}</pre><ul><li>Run terraform apply and check that the backup.zip file has been created</li><li>We are now going to create a resource to store the code of our cloud function in the db-backups bucket. In the main.tf of db-backups, add a new resource :</li></ul><pre>resource &quot;google_storage_bucket_object&quot; &quot;archive&quot; {<br>  name   = &quot;cloudFunctions/backup-${lower(replace(base64encode(data.archive_file.backupZipFile.output_md5), &quot;=&quot;, &quot;&quot;))}.zip&quot;<br>  bucket = google_storage_bucket.db-backups.name<br>  # Source path is relative to where the module is used : to improve<br>  source     = data.archive_file.backupZipFile.output_path<br>  depends_on = [data.archive_file.backupZipFile]<br>}</pre><p>You may be surprised by the name attribute of the archive but this is necessary: the cloud function to which we will give this archive will not detect changes made in this file unless its name is changed. This is why we chose a name for this file that will change only when its content has been modified.</p><ul><li>Run terraform apply to create this archive. In the <a href="https://console.cloud.google.com/storage/">Storage menu of GCP</a>, in the db-backups bucket, you should now see a folder cloudFunctions containing a zip file</li></ul><p><strong>4.2 Authorize your database to write in the db-backups bucket</strong></p><ul><li>Add a new resource in db-backups/main.tf :</li></ul><pre>resource &quot;google_storage_bucket_iam_member&quot; &quot;editor&quot; {<br>  bucket = google_storage_bucket.db-backups.name<br>  role   = &quot;roles/storage.objectCreator&quot;<br>  member = &quot;serviceAccount:${var.service_account_email}&quot;<br>}</pre><ul><li>Add a new variable in db-backups/variables.tf :</li></ul><pre>variable &quot;service_account_email&quot; {}</pre><ul><li>Then, in production/main.tf :</li></ul><pre>module &quot;db_backups&quot; {<br>  source                = &quot;../modules/db-backups&quot;<br>  project_id            = var.project_id<br>  <strong>service_account_email = google_sql_database_instance.your-database.service_account_email_address</strong><br>}</pre><ul><li>Run terraform apply</li></ul><p>Your database is now allowed to create files in your db-backups bucket</p><p><strong>4.3 Create the cloud function resource</strong></p><ul><li>Before doing anything, you need to enable the <a href="https://console.developers.google.com/apis/api/sqladmin.googleapis.com/">Cloud SQL Admin API</a> or your cloud function won’t work</li><li>In db-backups/main.tf, add a new resource :</li></ul><pre>resource &quot;google_cloudfunctions_function&quot; &quot;backupFunction&quot; {<br>  name        = &quot;backup&quot;<br>  description = &quot;Performs a backup of the specified database in the db-backups bucket&quot;<br>  runtime     = &quot;nodejs10&quot;<br>  project     = var.project_id</pre><pre>  available_memory_mb   = 128<br>  source_archive_bucket = google_storage_bucket.db-backups.name<br>  source_archive_object = google_storage_bucket_object.archive.name<br>  trigger_http          = true<br>  entry_point           = &quot;backup&quot;</pre><pre>environment_variables = {<br>    PROJECT_ID        = var.project_id,<br>    DB_INSTANCE_NAME  = var.db_instance_name,<br>    BUCKET_NAME       = google_storage_bucket.db-backups.name<br>    DB_NAME_TO_EXPORT = var.db_name_to_export<br>  }<br>}</pre><ul><li>As you can see, some more variables are needed : db_instance_name and db_name_to_export. Let’s add them in db-backups/variables.tf :</li></ul><pre>variable &quot;app_db_instance_name&quot; {}<br>variable &quot;db_name_to_export&quot; {}</pre><ul><li>Then, in production/main.tf :</li></ul><pre>module &quot;db_backups&quot; {<br>  source                = &quot;../modules/db-backups&quot;<br>  project_id            = var.project_id<br>  service_account_email = google_sql_database_instance.your-database.service_account_email_address<strong><br>  app_db_instance_name  = google_sql_database_instance.your-database.name<br>  db_name_to_export     = &quot;name-of-the-database-to-export</strong>&quot;<br>}</pre><ul><li>After running terraform apply, go in the <a href="https://console.cloud.google.com/functions">Cloud Functions menu of GCP</a> and you should see your cloud function named “backup” there</li><li>You can already test it (in the “Actions” column, select “Test function”, and then click on “Test the function” without adding anything in the “Triggering event”). Depending on the size of your database, the backup could take long. But ultimately, you should see a backup appears in your db-backups bucket!</li></ul><p><strong>4.4 Deal with the “Error waiting for Creating CloudFunctions Function”</strong></p><p>When trying to create the cloud function with terraform apply, you may encounter this error :</p><pre>[...] googleapi: Error 403: Your application has authenticated using end user credentials from the Google Cloud SDK or Google Cloud Shell which are not supported by the cloudfunctions.googleapis.com. We recommend configuring the billing/quota_project setting in gcloud or using a service account through the auth/impersonate_service_account setting. [...]</pre><p>Terraform does not allow end-users to create cloud functions, only service accounts are authorized to do so. So here is a trick to be able to create your cloud function from your terminal anyway :</p><ul><li>In db-backups/main.tf, create a service account that will have the right permissions (💡 if you already have one <strong><em>with the right permissions</em></strong>, you can skip this creation step and use it for the following steps)</li></ul><pre>resource &quot;google_service_account&quot; &quot;cloudFunction&quot; {<br>  project      = var.project_id<br>  account_id   = &quot;cloud-function&quot;<br>  display_name = &quot;cloud function&quot;<br>  description  = &quot;Used to create cloud functions&quot;<br>}</pre><pre>resource &quot;google_project_iam_member&quot; &quot;cloudFunctionEditor&quot; {<br>  project = var.project_id<br>  role    = &quot;roles/editor&quot;<br>  member  = &quot;serviceAccount:${google_service_account.cloudFunction.email}&quot;<br>}<br>resource &quot;google_project_iam_member&quot; &quot;cloudFunctionStorageAdmin&quot; {<br>  project = var.project_id<br>  role    = &quot;roles/storage.admin&quot;<br>  member  = &quot;serviceAccount:${google_service_account.cloudFunction.email}&quot;<br>}</pre><ul><li>In the <a href="https://console.cloud.google.com/iam-admin/serviceaccounts">Service Accounts menu of GCP</a>, create a key for this service account</li><li>If you have jq, you can use this command to get a well-formatted output of the key (if not, remove all spaces and line breaks except for those in BEGIN PRIVATE KEY and END PRIVATE KEY) :</li></ul><pre>cat &lt;your_file_path&gt;.json | jq --compact-output</pre><ul><li>Copy the output and then run the following command (don’t forget the &#39; around the google credentials) :</li></ul><pre>GOOGLE_CREDENTIALS=&#39;&lt;paste_key_output_here&gt;&#39; terraform apply</pre><h3>5. Create a cloud scheduler to trigger a daily backup of your database</h3><p><strong>5.1 Configure an app engine</strong></p><ul><li>We now have a working cloud function to trigger a backup of our database. Let’s say we want to make a daily backup at 4 a.m in the french timezone.</li><li>First of all, enable the <a href="https://console.cloud.google.com/apis/api/cloudscheduler.googleapis.com/">Cloud Scheduler API</a> in your project</li><li>Then, to be able to use a cloud scheduler, your project must contain an App Engine. If you don’t, you need to create one.</li><li>In production/main.tf, add a new resource :</li></ul><pre>resource &quot;google_app_engine_application&quot; &quot;app_engine&quot; {<br>  project     = var.project_id<br>  location_id = &quot;europe-west&quot;<br>}</pre><p>⚠️ once an App Engine is created, you <strong>can not</strong> delete it or edit its location_id. The only way to do so would be to delete the entire project so define it carefully!</p><ul><li>Run terraform plan : if you did not already have an app engine, Terraform will ask to create it. If you are sure about your configuration, do it. If you already did have one, Terraform will throw you an error saying that there is already an existing app engine.</li><li>(Do this step only if you already have an app engine) :</li></ul><pre>terraform import google_app_engine_application.app_engine your-project-id</pre><p><strong>5.2 Create the cloud scheduler</strong></p><ul><li>Add a new resource in db-backups/main.tf :</li></ul><pre>resource &quot;google_cloud_scheduler_job&quot; &quot;backupJob&quot; {<br>  project     = var.project_id<br>  region      = var.scheduler_region<br>  name        = &quot;backup-job&quot;<br>  description = &quot;Create a backup of the database&quot;<br>  schedule    = &quot;0 4 * * *&quot;<br>  time_zone   = &quot;Europe/Paris&quot;</pre><pre>http_target {<br>    http_method = &quot;POST&quot;<br>    uri         = google_cloudfunctions_function.backupFunction.https_trigger_url<br>  }<br>}</pre><ul><li>A new variable scheduler_region is needed, because this value should be compatible with the location you set for your app engine. In db-backups/variables.tf, add :</li></ul><pre>variable &quot;scheduler_region&quot; {}</pre><ul><li>Then, in production/main.tf :</li></ul><pre>module &quot;db_backups&quot; {<br>  source                = &quot;../modules/db-backups&quot;<br>  project_id            = var.project_id<br>  service_account_email = google_sql_database_instance.your-database.service_account_email_address<br>  app_db_instance_name  = google_sql_database_instance.your-database.name<br>  <strong>scheduler_region      = &quot;europe-west1&quot;</strong><br>}</pre><p>⚠️ in this example, the app engine has a location_id = &quot;europe-west&quot; that’s why I defined scheduler_region = &quot;europe-west1&quot;. If your app engine has another location_id, find the compatible region to set your scheduler, or Terraform will throw an error</p><ul><li>Run terraform apply</li><li>Go in the <a href="https://console.cloud.google.com/cloudscheduler">Cloud Scheduler menu of GCP</a>: you should see your scheduler ready to work!</li><li>You can test it right away by clicking the “Test now” button: a backup should then appear in your db-backups bucket!</li></ul><p>Congratulations! Daily backups are now scheduled for your database.</p><p>Feel free to share your comments or questions below!</p><p><em>Drug discovery is a challenging, intellectually complex, and rewarding endeavor: we help develop effective and safe cures to diseases affecting millions of people. If you’re looking to have a massive impact, join us! </em><a href="https://inato.com/careers"><em>inato.com/careers</em></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8688da8180e1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/inato/how-to-use-terraform-to-schedule-backups-for-your-google-cloud-sql-database-8688da8180e1">How to use Terraform to schedule backups for your Google Cloud SQL database</a> was originally published in <a href="https://medium.com/inato">inato</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>