<?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 Ali Yıldızöz on Medium]]></title>
        <description><![CDATA[Stories by Ali Yıldızöz on Medium]]></description>
        <link>https://medium.com/@aliyildizoz?source=rss-a62137e94030------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*szFgfevLkBFrvE6-Zk3HUQ.jpeg</url>
            <title>Stories by Ali Yıldızöz on Medium</title>
            <link>https://medium.com/@aliyildizoz?source=rss-a62137e94030------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 29 May 2026 17:51:42 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@aliyildizoz/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[Docker-Compose for Asp.Net Core & Elasticsearch]]></title>
            <link>https://medium.com/@aliyildizoz/docker-compose-for-asp-net-core-elasticsearch-40eb8a23ff0a?source=rss-a62137e94030------2</link>
            <guid isPermaLink="false">https://medium.com/p/40eb8a23ff0a</guid>
            <category><![CDATA[elasticsearch]]></category>
            <category><![CDATA[kibana]]></category>
            <category><![CDATA[aspnetcore]]></category>
            <category><![CDATA[docker-compose]]></category>
            <category><![CDATA[docker]]></category>
            <dc:creator><![CDATA[Ali Yıldızöz]]></dc:creator>
            <pubDate>Sun, 06 Oct 2024 19:41:39 GMT</pubDate>
            <atom:updated>2024-10-06T20:34:29.469Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ixxXGodteLi4Vt9vVEBOLA.png" /></figure><p>Elasticsearch is a distributed, RESTful engine and a NoSQL database for fast searching. However, it has a significant difference than other databases, including NoSQL databases. When data is saved, Elasticsearch analyzes it (e.g., tokenization, stemming) and stores it in an inverted index, allowing for rapid full-text search.</p><p>In this article, I will demonstrate how to configure a Docker-Compose environment for an ASP.NET Core Web API and Elasticsearch.</p><h3>Example Application</h3><p>Our application will be a basic application. It will perform read, add, and remove operations on the product index.</p><p><strong>Nest </strong>and <strong>Elastic.Clients.Elasticsearch</strong> are two main libraries to use Elasticsearch in Asp.Net Core applications. In our example, we will use<strong> Elastic.Clients.Elasticsearch</strong> library which is recommended by Elasticsearch.</p><h4>appsettings.Production.json</h4><pre>  &quot;ElasticsearchOption&quot;: {<br>    &quot;Url&quot;: &quot;http://elasticsearch-server:9200&quot;,<br>    &quot;Username&quot;: &quot;elastic&quot;,<br>    &quot;Password&quot;: &quot;changeme&quot;<br>  }</pre><p>I added a ElasticsearchOption section in the appsettings. The Elasticsearch service in our docker-compose file is named elasticsearch-server, so I’ve configured the Elasticsearch server name accordingly.</p><h4>Program.cs</h4><pre>var userName = builder.Configuration.GetSection(&quot;ElasticsearchOption&quot;)[&quot;Username&quot;];<br>var password = builder.Configuration.GetSection(&quot;ElasticsearchOption&quot;)[&quot;Password&quot;];<br>var settings = new ElasticsearchClientSettings(new Uri(builder.Configuration.GetSection(&quot;ElasticsearchOption&quot;)[&quot;Url&quot;]!)).Authentication( new BasicAuthentication(userName!,password!));<br><br>var client= new ElasticsearchClient(settings);<br><br>builder.Services.AddSingleton(client);<br><br>builder.Services.AddScoped&lt;ElasticsearchService&gt;();</pre><p>I created ElasaticsearchClientSettings object to connect with Elasticsearch, and registered ElasticsearchClientfor search operations as above.</p><h3>Docker-Compose</h3><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ea50ef5acccfd8c3cea574a0785e7397/href">https://medium.com/media/ea50ef5acccfd8c3cea574a0785e7397/href</a></iframe><p>We have three services here our Web API application, Elasticsearch server and Kibana.</p><h4>asp-net-core-elasticsearch</h4><pre> asp-net-core-elasticsearch:<br>    container_name: asp-net-core-elasticsearch<br>    image: aliyildizoz909/docker-demo-elasticsearch<br>    ports:<br>      - &quot;5000:8080&quot;<br>    depends_on:<br>      - elasticsearch-server</pre><ul><li><strong>container_name: asp-net-core-elasticsearch</strong>: We set the container name as asp-net-core-elasticsearch.</li><li><strong>image: aliyildizoz909/docker-demo-elasticsearch</strong>: We specify the image source as <a href="https://hub.docker.com/r/aliyildizoz909/docker-demo-elasticsearch">aliyildizoz909/docker-demo-elasticsearch</a> .</li><li><strong>&quot;5000:8080&quot; : </strong>Here, we did a port publish. When we go to the <strong>5000 </strong>port via localhost, we tell the docker to redirect the requests to the <strong>8080 </strong>port in the asp-net-core-elasticsearchcontainer. <strong>8080 </strong>port is the default port of our published app in the container for asp.net core apps. You can change it via the ASPNETCORE_URLS environment variable or with the new one ASPNETCORE_HTTP_PORTS . <a href="https://learn.microsoft.com/en-us/dotnet/core/compatibility/containers/8.0/aspnet-port">See more information</a>.</li><li><strong>depends_on </strong>This section defines the priority of services to run. We add here which services we want to run before the current service. So, in our example, we add elasticsearch-server here to run our application after the Elasticsearch server is ready.</li></ul><h4>elasticsearch-server</h4><pre> elasticsearch-server:<br>    container_name: elasticsearch<br>    image: docker.elastic.co/elasticsearch/elasticsearch:8.7.1<br>    ports:<br>      - &quot;9200:9200&quot;<br>    environment:<br>      - xpack.security.enabled=false<br>      - &quot;discovery.type=single-node&quot;</pre><ul><li><strong>container_name: elasticsearch-server</strong>: We set the container name as elasticsearch-server .</li><li><strong>image: elasticsearch</strong> : We specify the image source as <a href="https://hub.docker.com/_/elasticsearch">elasticsearch</a>.</li><li><strong>9200: </strong>This port represent the Elasticsearch server.</li><li><strong>environment: </strong><strong>xpack.security.enabled=false </strong>means that Elasticsearch will not require authentication, allowing you to test any application without security restrictions. This is useful in development environments. However, if you set it to true, you&#39;ll need to configure additional security settings such as user authentication, role-based access control (RBAC), and TLS/SSL for secure communication.</li><li><strong>environment: </strong>The discovery.type=single-node setting in Elasticsearch configures the cluster to run in <strong>single-node mode</strong>. This means Elasticsearch will not attempt to discover or connect to other nodes, and it assumes that the node running with this configuration is the only one in the cluster.</li></ul><h4>kibana</h4><pre>kibana:<br>    container_name: kibana<br>    image: docker.elastic.co/kibana/kibana:8.7.1<br>    environment:<br>      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200<br>    ports:<br>      - &quot;5601:5601&quot;<br>    depends_on:<br>      - elasticsearch-server </pre><ul><li><strong>container_name: kibana</strong>: We set the container name as kibana.</li><li><strong>image: docker.elastic.co/kibana/kibana:8.7.1</strong>: We specify the image source as <a href="https://hub.docker.com/_/kibana">docker.elastic.co/kibana/kibana:8.7.1</a>.</li><li><strong>5601: </strong>This port represent the Kibana UI.</li><li><strong>environment:</strong> ELASTICSEARCH_HOSTS=http://elasticsearch:9200 is used to specify the <strong>URL of the Elasticsearch instance</strong> that your kibana will connect to.</li></ul><h3>Running Docker-Compose File</h3><p>To run our Docker-Compose setup, use the following command from the directory containing your docker-compose.yml file</p><pre>docker compose up</pre><p>After running this command, we can access our application here <a href="http://localhost:5000/swagger/index.html">http://localhost:5000</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LF183o30XpyvEobdSqnWgw.png" /><figcaption>Docker Desktop</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rp4V0JfQUXqkatpGt4XSdg.png" /><figcaption><a href="http://localhost:5000/swagger/index.html">http://localhost:5000/swagger/index.html</a></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iOKa-KaHf6YqSMWYMPn2NQ.png" /><figcaption>Kibana</figcaption></figure><p>You can see the app from <a href="https://github.com/aliyildizoz/DockerDemo/tree/docker-compose-elasticsearch-demo">here</a>.</p><p>Thank you.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=40eb8a23ff0a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Async Programming: await vs Task.Wait() vs Task.WaitAll()]]></title>
            <link>https://medium.com/@aliyildizoz/async-programming-await-vs-task-wait-vs-task-waitall-8048ecb14a66?source=rss-a62137e94030------2</link>
            <guid isPermaLink="false">https://medium.com/p/8048ecb14a66</guid>
            <category><![CDATA[tap]]></category>
            <category><![CDATA[c-sharp-programming]]></category>
            <category><![CDATA[task-based-async-pattern]]></category>
            <category><![CDATA[tasks]]></category>
            <category><![CDATA[asynchronous-programming]]></category>
            <dc:creator><![CDATA[Ali Yıldızöz]]></dc:creator>
            <pubDate>Sat, 15 Jun 2024 00:47:58 GMT</pubDate>
            <atom:updated>2024-06-15T00:50:50.081Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vVu9lBdgELW1s2a5AhDFcA.png" /></figure><p>Async programming is a paradigm that allows us to run multiple tasks concurrently with other tasks without blocking the execution flow. This technique is very useful for performing tasks that take a long time to complete. Because these tasks do not block the main thread, they increase application performance.</p><p>In asynchronous programming, we utilize the <strong>await</strong> keyword and the <strong>Task.Wait()</strong> and <strong>Task.WaitAll() </strong>methods. While these methods may seem similar, there are significant differences. This article will explore the distinctions between <strong>await, Task.Wait()</strong>, and <strong>Task.WaitAll()</strong>.</p><h3>await Keyword</h3><p>Using this keyword initiates the task asynchronously, meaning it operates on a separate thread or subsystem without blocking the main thread. When this keyword is employed, the code pauses until the task concludes on the other thread, allowing the main thread to process other tasks concurrently.</p><h4>Usage-1</h4><pre>var result = await DoSomethingAsync();</pre><p>This usage pauses the execution of the current method at this line of code until the task completes. However, it doesn’t block the main thread, allowing it to continue executing other operations.</p><h4>Usage-2</h4><pre>var task = DoSomethingAsync();</pre><p>This usage starts the asynchronous task without pausing the current method, allowing subsequent code to execute immediately. The task runs in the background, freeing the main thread to perform other operations concurrently.</p><h3>Task.Wait()</h3><p>Unlike await, Task.Wait() converts asynchronous code to synchronous by blocking the main thread until the task completes.</p><h4>Usage</h4><pre>var task = DoSomethingAsync();<br>task.Wait();<br>var result = task.Result;</pre><p>Be careful when using this method because it will block the main thread until it completes the task, preventing any other operations until the main thread is released.</p><h4>Task.Result</h4><pre>var task = DoSomethingAsync();<br>var result = task.Result;<br>//OR<br>var task = DoSomethingAsync().Result;</pre><p>Accessing Task.Result works similarly to Task.Wait(), blocking the main thread until the task completes.</p><h3>Task.WaitAll()</h3><p>While Task.WaitAll() accepts multiple tasks and allows them to execute asynchronously, the method itself runs synchronously, blocking the main thread until all tasks have completed.</p><pre>Task.WaitAll(task1,task2,task3, ....);</pre><p>Again, this method blocks the main thread and waits synchronously until all tasks are completed but it executes the tasks asynchronously.</p><p>Thanks.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8048ecb14a66" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Docker-Compose for Asp.Net Core & Redis]]></title>
            <link>https://blog.devops.dev/docker-compose-for-asp-net-core-redis-b739b9242765?source=rss-a62137e94030------2</link>
            <guid isPermaLink="false">https://medium.com/p/b739b9242765</guid>
            <category><![CDATA[docker-compose]]></category>
            <category><![CDATA[aspnetcore]]></category>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[caching]]></category>
            <dc:creator><![CDATA[Ali Yıldızöz]]></dc:creator>
            <pubDate>Sun, 02 Jun 2024 00:05:29 GMT</pubDate>
            <atom:updated>2024-06-04T15:31:13.647Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ARWjO6jCL4Hw3osr_AWqmQ.png" /></figure><p>Caching is one of the important subjects in the software field. For reducing our application response time and improve the performance, caching is a very helpful tool.</p><p>There are two types of caching mechanisms.</p><ul><li><strong>In-Memory Caching: </strong>The data are kept in the host’s memory on which the application resides.</li><li><strong>Distributed Caching: </strong>The data are kept on a different server in which the application doesn’t reside.</li></ul><p>Redis is a Distributed Caching tool. There are some advantages and disadvantages for both caching mechanisms but I won’t explain them in this article. In this article, I will show you how to create a docker-compose file for an application that uses Redis.</p><h3>Example Application</h3><p>Our application will be a basic to-do application. It will perform read, add, and remove operations on the cache.</p><p>There are many libraries to use Redis in Asp.Net Core applications. In our example, we will use<strong> StackExchange.Redis</strong> library.</p><h4>StackExchange.Redis</h4><p>This is the most popular NuGet package for utilizing Redis with full functionality. It provides access to all redis-cli commands, it is very useful for complex scenarios. However, the implementation may be slightly more difficult compared to other methods. It may require setting up a Redis service and configuring the connection to the Redis server. This package uses the IDatabase interface for executing Redis commands.</p><h4>appsettings.Production.json</h4><pre>  &quot;RedisOption&quot;: {<br>    &quot;Configuration&quot;: &quot;redis-server&quot;,<br>    &quot;DefaultDatabase&quot;: 0<br>  }</pre><p>I added a RedisOption section to the appsettings. The Redis service in our docker-compose file is named redis-server, so I’ve configured the Redis server name accordingly. There are many databases numbered in the Redis server, I used 0 database as the default.</p><h4>RedisService</h4><pre> public class RedisService<br> {<br>     private ConnectionMultiplexer _redis;<br><br>     public IDatabase Database { get; set; }<br>     public RedisService(IConfiguration configuration)<br>     {<br>         _redis = ConnectionMultiplexer.Connect(configuration.GetValue&lt;string&gt;(&quot;RedisOption:Configuration&quot;));<br>         Database = _redis.GetDatabase(configuration.GetValue&lt;int&gt;(&quot;RedisOption:DefaultDatabase&quot;));<br>     }<br><br>     public IDatabase GetDatabase(int db)<br>     {<br>         return _redis.GetDatabase(db);<br>     }<br> }</pre><p>I implemented a service to carry out general operations for Redis. Initially, it establishes a connection to Redis and then sets the default database.</p><h4>Program.cs</h4><pre>builder.Services.AddSingleton&lt;RedisService&gt;();<br>builder.Services.AddSingleton&lt;IDatabase&gt;(x =&gt;<br>{<br>    var redisService = x.GetService&lt;RedisService&gt;();<br>    return redisService.Database;<br>});</pre><p>I registered RedisService and IDatabase as singletons to be accessible in the controllers.</p><h4>ToDosController</h4><pre>    [HttpGet]<br>    public async Task&lt;IEnumerable&lt;string&gt;&gt; GetAsync()<br>    {<br>        if (_database.KeyExists(ToDosKey))<br>        {<br>            var cacheValue = await _database.ListRangeAsync(ToDosKey);<br>            return cacheValue.Select(x =&gt; x.ToString());<br>        }<br><br>        return [];<br>    }<br><br>    [HttpPost]<br>    public async Task&lt;string&gt; PostAsync(string todo)<br>    {<br>        await _database.ListRightPushAsync(ToDosKey, todo);<br>        return todo;<br>    }<br><br>    [HttpDelete]<br>    public async Task&lt;string&gt; DeleteAsync(string todo)<br>    {<br>        await _database.ListRemoveAsync(ToDosKey, todo);<br>        return todo;<br>    }</pre><p>We have a controller here for testing Redis. It essentially uses the <a href="https://redis.io/docs/latest/develop/data-types/lists/">Redis List type</a> to retrieve todos from the cache, adds a todo to the cache, and removes them from the cache.</p><p>You can see the app from <a href="https://github.com/aliyildizoz/DockerDemo/tree/docker-compose-redis-demo">here</a>.</p><h3>Docker-Compose</h3><p>Actually, integrating Redis into a Docker container for use with an Asp.Net Core application, or any other application, is quite straightforward. You just have to make sure that the Redis service name in the docker-compose file corresponds with the Redis server’s configuration name in your application’s connection settings.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4bade9c1ad0eb87d26d757ae2f2f8b03/href">https://medium.com/media/4bade9c1ad0eb87d26d757ae2f2f8b03/href</a></iframe><p>We have two services here our Web API application and Redis server.</p><h3>asp-net-core-redis</h3><pre>asp-net-core-redis:<br>    container_name: asp-net-core-redis<br>    image: aliyildizoz909/docker-demo-redis<br>    ports:<br>      - &quot;5000:8080&quot;<br>    depends_on:<br>      - redis-server</pre><ul><li><strong>container_name: asp-net-core-redis</strong>: We set the container name as asp-net-redis .</li><li><strong>image: aliyildizoz909/docker-demo-redis</strong> : We specify the image source as <a href="https://hub.docker.com/r/aliyildizoz909/docker-demo-redis">aliyildizoz909/docker-demo-redis</a> .</li><li><strong>&quot;5000:8080&quot; : </strong>Here, we did a port publish. When we go to the <strong>5000 </strong>port via localhost, we tell the docker to redirect the requests to the <strong>8080 </strong>port in the asp-net-core-rediscontainer. <strong>8080 </strong>port is the default port of our published app in the container for asp.net core apps. You can change it via the ASPNETCORE_URLS environment variable or with the new one ASPNETCORE_HTTP_PORTS . <a href="https://learn.microsoft.com/en-us/dotnet/core/compatibility/containers/8.0/aspnet-port">See more information</a>.</li><li><strong>depends_on </strong>This section defines the priority of services to run. We add here which services we want to run before the current service. So, in our example, we add redis-server here to run our application after the Redis server is ready.</li></ul><h3>redis-server</h3><pre>  redis-server:<br>    container_name: redis-server<br>    image: redis<br>    ports:<br>      - &quot;6379:6379&quot;</pre><ul><li><strong>container_name: redis-server</strong>: We set the container name as redis-server .</li><li><strong>image: redis</strong> : We specify the image source as <a href="https://hub.docker.com/_/redis">redis</a>.</li><li><strong>6379:6379: </strong>These ports represent the Redis server.</li></ul><h3>Running Docker-Compose File</h3><p>To run our Docker-Compose setup, use the following command from the directory containing your docker-compose.yml file</p><pre>docker compose up</pre><p>After running this command, we can access our application here <a href="http://localhost:5000/swagger/index.html">http://localhost:5000</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*a58F_uzD4uF9_cjts9e0SQ.png" /><figcaption>Docker Desktop</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*umhDwi9pCFB_PdQPeg7zoA.png" /><figcaption><a href="http://localhost:5000/swagger/index.html">http://localhost:5000/swagger/index.html</a></figcaption></figure><p>Thank you.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b739b9242765" width="1" height="1" alt=""><hr><p><a href="https://blog.devops.dev/docker-compose-for-asp-net-core-redis-b739b9242765">Docker-Compose for Asp.Net Core &amp; Redis</a> was originally published in <a href="https://blog.devops.dev">DevOps.dev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Docker-Compose for Asp.Net Core & RabbitMQ]]></title>
            <link>https://blog.devops.dev/docker-compose-for-asp-net-core-rabbitmq-d532125b1cec?source=rss-a62137e94030------2</link>
            <guid isPermaLink="false">https://medium.com/p/d532125b1cec</guid>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[aspnetcore]]></category>
            <category><![CDATA[webapi]]></category>
            <category><![CDATA[rabbitmq]]></category>
            <category><![CDATA[docker-compose]]></category>
            <dc:creator><![CDATA[Ali Yıldızöz]]></dc:creator>
            <pubDate>Sun, 19 May 2024 18:39:52 GMT</pubDate>
            <atom:updated>2024-05-22T11:48:39.598Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eEcWBLlttoq_1NqHYiSTng.png" /></figure><p>RabbitMQ is a popular open-source message broker. It is easy to implement in your application and manage your queue, exchange, and messages. It supports many programming languages. Also, you can use it with many technologies, such as web apps, RPC, Streaming, or IoT.</p><p>In this article, I will demonstrate how to configure a Docker-Compose environment for an ASP.NET Core Web API and RabbitMQ.</p><h3>Example Application</h3><p>In our application, we will have two projects. We will send a weather forecast notification to our queue via the Asp.Net Core Web API application and then receive the message in the Worker Service application.</p><ol><li><strong>Asp.Net Core Web API Application: </strong>This app will be our publisher. We will make an HTTP Post request with Swagger to publish our message.</li><li><strong>Worker Service Application: </strong>This app will be our consumer. We will receive messages here.</li></ol><h4>Connection String Configuration</h4><p>When our apps run in a container they will be in the Production environment. So I added the RabbitMQ connection string to the appsettings.Production.json file.</p><pre>  &quot;ConnectionStrings&quot;: {<br>    &quot;RabbitMQ&quot;: &quot;amqp://guest:guest@docker-rabbitmq-server:5672&quot;<br>  }</pre><p>As you see I gave the server name as docker-rabbitmq-server . This name corresponds to the RabbitMQ service name in the docker-compose file. Docker provides a DNS service between containers in the same network. So we don&#39;t have to give the server IP address. It is important that we use the same connection string in both projects. They have to connect to the same RabbitMQ server.</p><p>I used the default username and password for RabbitMQ so I didn’t add an Environment Variable for those fields. You can provide those fields with environment variables at runtime for security vulnerabilities.</p><h4>Sending Message</h4><pre>[HttpPost]<br>public IActionResult Post()<br>{<br>    var weatherForecast = new WeatherForecast<br>    {<br>        Date = DateTime.Now,<br>        TemperatureC = Random.Shared.Next(-20, 55),<br>        Summary = Summaries[Random.Shared.Next(Summaries.Length)]<br>    };<br><br>    _rabbitMQPublisher.Publish(weatherForecast);<br><br>    return Ok(weatherForecast);<br>}</pre><p>After running the web API, we will make a post request to this action. As you see, it creates a random temperature and summary and then publishes it.</p><h4>Receiving Message</h4><pre>public override Task StartAsync(CancellationToken cancellationToken)<br> {<br>     _channel = _rabbitMQClientService.Connect();<br>     _channel.BasicQos(0, 1, false);<br>     return base.StartAsync(cancellationToken);<br> }<br> protected override async Task ExecuteAsync(CancellationToken stoppingToken)<br> {<br>     var consumer = new AsyncEventingBasicConsumer(_channel);<br>     _channel.BasicConsume(RabbitMQClientService.QueueName, false, consumer);<br>     consumer.Received += Consumer_Received;<br> }<br><br> private async Task Consumer_Received(object sender, BasicDeliverEventArgs @event)<br> {<br>     var weatherForecast = JsonSerializer.Deserialize&lt;WeatherForecast&gt;(Encoding.UTF8.GetString(@event.Body.ToArray()));<br><br>     Console.WriteLine(&quot;\nNew Weather Forecast Notification!!!&quot;);<br>     Console.WriteLine($&quot;{nameof(weatherForecast.Date)}:{weatherForecast.Date.ToString(&quot;f&quot;)} {nameof(weatherForecast.TemperatureF)}:{weatherForecast.TemperatureF} {nameof(weatherForecast.TemperatureC)}:{weatherForecast.TemperatureC} {nameof(weatherForecast.Summary)}:{weatherForecast.Summary}&quot;);<br><br>     _channel.BasicAck(@event.DeliveryTag, false);<br> }</pre><p>I chose the worker service as the consumer app because it has built-in DI(Dependency Injection), Configuration tool, appsettings file, etc. You can do anything you want here what you do in an Asp.Net Core App too. Our worker service will run automatically after the app is launched.</p><p>Here we connected to the Received event. If the queue receives a message, it will trigger this event.</p><p>You can see the app from <a href="https://github.com/aliyildizoz/DockerDemo/tree/docker-compose-rabbitmq-demo">here</a>.</p><h3>Docker-Compose</h3><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6dae562f63e29ba91c1c19533a46c2d9/href">https://medium.com/media/6dae562f63e29ba91c1c19533a46c2d9/href</a></iframe><p>We have three services here: the first one is our web API the publisher, the second one is our worker service the consumer and the third one is the RabbitMQ server.</p><h4>asp-net-core-publisher</h4><pre> asp-net-core-publisher:<br>    container_name: asp-net-core-publisher<br>    image: aliyildizoz909/docker-demo-rabbitmq-publisher<br>    ports:<br>      - &quot;5000:8080&quot;<br>    depends_on:<br>      - docker-rabbitmq-server   </pre><ul><li><strong>container_name: asp-net-core-publisher</strong>: We set the container name as asp-net-publisher</li><li><strong>image: aliyildizoz909/docker-demo-rabbitmq-publisher</strong> : We specify the image source as <a href="https://hub.docker.com/r/aliyildizoz909/docker-demo-rabbitmq-publisher">aliyildizoz909/docker-demo-rabbitmq-publisher</a></li><li><strong>&quot;5000:8080&quot; : </strong>Here, we did a port publish. When we go to the <strong>5000 </strong>port via localhost, we tell the docker to redirect the requests to the <strong>8080 </strong>port in the asp-net-core-publishercontainer. <strong>8080 </strong>port is the default port of our published app in the container for asp.net core apps. You can change it via the ASPNETCORE_URLS environment variable or with the new one ASPNETCORE_HTTP_PORTS . <a href="https://learn.microsoft.com/en-us/dotnet/core/compatibility/containers/8.0/aspnet-port">See more information</a>.</li><li><strong>depends_on </strong>This section defines the priority of services to run. We add here which services we want to run before the current service. So, in our example, we add docker-rabbitmq-server here to run our application after the RabbitMQ server is ready.</li></ul><h4>worker-service-consumer</h4><pre> worker-service-consumer:<br>    container_name: worker-service-consumer<br>    image: aliyildizoz909/docker-demo-rabbitmq-consumer<br>    depends_on:<br>      - docker-rabbitmq-server</pre><ul><li><strong>container_name: worker-service-consumer</strong>: We set the container name as worker-service-consumer</li><li><strong>image: aliyildizoz909/docker-demo-rabbitmq-consumer</strong> : We specify the image source as <a href="https://hub.docker.com/r/aliyildizoz909/docker-demo-rabbitmq-consumer">aliyildizoz909/docker-demo-rabbitmq-consumer</a></li><li><strong>depends_on: </strong>Before the application runs the RabbitMQ server should be ready otherwise, it will not be able to connect and will give an error.</li></ul><h4>docker-rabbitmq-server</h4><pre>  docker-rabbitmq-server:<br>    container_name: docker-rabbitmq-server<br>    image: rabbitmq:3-management<br>    ports:<br>      - &quot;15672:15672&quot;<br>      - &quot;5672:5672&quot;</pre><ul><li><strong>container_name: docker-rabbitmq-server</strong>: We set the container name as docker-rabbitmq-server</li><li><strong>image: rabbitmq:3-management</strong> : We specify the image source as <a href="https://hub.docker.com/_/rabbitmq">rabbitmq:3-management</a>. I utilized the 3-management tag for this image to enable the RabbitMQ management plugin for monitoring exchanges, queues, and messages. This tag will provide a management tool at localhost:15672 .</li><li><strong>15672:15672: </strong>These ports represent the management plugin.</li><li><strong>5672:5672: </strong>These ports represent the RabbitMQ server.</li></ul><h3>Running Docker-Compose File</h3><p>To run our Docker-Compose setup, use the following command from the directory containing your docker-compose.yml file</p><pre>docker compose up</pre><p>After running this command, we can access our application here <a href="http://localhost:5000/swagger/index.html">http://localhost:5000</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eB8i11T4ojZ3h7trs7h2rA.png" /><figcaption>Docker Desktop</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wZ33RKfQgTKMsWvdiwfpQQ.png" /><figcaption><a href="http://localhost:5000/swagger/index.html">http://localhost:5000/swagger/index.html</a></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xukIE2aDIvuYa0jff_1Evg.png" /><figcaption><a href="http://localhost:15672/#/exchanges">http://localhost:15672/#/exchanges</a></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ieBERFvVEzV6Zv7LBEIVtQ.png" /><figcaption>Sending and receiving messages.</figcaption></figure><p>Thank you.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d532125b1cec" width="1" height="1" alt=""><hr><p><a href="https://blog.devops.dev/docker-compose-for-asp-net-core-rabbitmq-d532125b1cec">Docker-Compose for Asp.Net Core &amp; RabbitMQ</a> was originally published in <a href="https://blog.devops.dev">DevOps.dev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Docker-Compose for Asp.Net Core & MSSQL]]></title>
            <link>https://medium.com/@aliyildizoz/docker-compose-for-asp-net-core-mssql-4ee8317d3b87?source=rss-a62137e94030------2</link>
            <guid isPermaLink="false">https://medium.com/p/4ee8317d3b87</guid>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[docker-compose]]></category>
            <category><![CDATA[sql-server]]></category>
            <category><![CDATA[aspnetcore]]></category>
            <category><![CDATA[entity-framework-core]]></category>
            <dc:creator><![CDATA[Ali Yıldızöz]]></dc:creator>
            <pubDate>Wed, 24 Apr 2024 00:03:23 GMT</pubDate>
            <atom:updated>2024-04-24T00:19:57.827Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*T4qvqDcXLu9HY0pLizqyhg.png" /></figure><p>Docker Compose is a tool that helps us run multiple containers together in harmony with one command. With this tool, we can easily test and preview our applications very quickly. We just define our services(containers), volumes, and networks in this file; Docker creates them for us. Docker Compose is usually used for development and testing, but with each release, Docker is making progress on more production-oriented features, too.</p><p>In this blog, I explain how to connect an Asp.Net Core Web API application to an MSSQL database in a docker container environment.</p><p>I created the application. It performs crud operations on a table. You can view the application from <a href="https://github.com/aliyildizoz/DockerDemo/tree/docker-compose-demo">here</a>.</p><h3>Connection String Configuration</h3><p>If we consider that our connection be changed in the production, we must create another appsettings file for the production. So, below, I added a ConnectionString section in the appsettings.Production.json file.</p><pre>{<br>  &quot;Logging&quot;: {<br>    &quot;LogLevel&quot;: {<br>      &quot;Default&quot;: &quot;Information&quot;,<br>      &quot;Microsoft.AspNetCore&quot;: &quot;Warning&quot;<br>    }<br>  },<br>  &quot;ConnectionStrings&quot;: {<br>    &quot;DefaultConnection&quot;: &quot;Server=docker-demo-sqlserver,1433;Database=DockerComposeDemo;User=sa;Password={0};TrustServerCertificate=True&quot;<br>  }<br>}<br><br></pre><p>The most important feature of docker-compose is connecting with other containers using their names. Docker provides a DNS service for us here. So here in the Connection string, I use the default port number of the SQL server image and use docker-compose-demo-sqlserver as the server name. I use this name as the name of the SQL server service within the docker-compose file in the following part.</p><p>Because of security vulnerabilities, we shouldn’t add the SQL server password into the app settings. In this example, we create an environment variable for the password and provide it dynamically when it runs.</p><pre>builder.Services.AddDbContext&lt;DockerComposeDemoDbContext&gt;(<br>                       options =&gt;<br>                       {<br>                           var connectionString = builder.Configuration.GetConnectionString(&quot;DefaultConnection&quot;);<br>                           if (!builder.Environment.IsDevelopment())<br>                           {<br>                               var password = Environment.GetEnvironmentVariable(&quot;MSSQL_SA_PASSWORD&quot;);<br>                               connectionString = string.Format(connectionString, password);<br>                           }<br>                           options.UseSqlServer(connectionString);<br><br>                       });</pre><p>As you see, we first check the environment. If it is not Development, we provide the password from the environment variable called MSQL_SA_PASSWORD. The app provides the connection string to our DbContext at runtime, so we don’t have to add the password in the app settings.</p><h4>Migration at startup</h4><p>I apply migration and create the database at runtime. For that, I add the following code to the Program.cs file before the application is launched. When the app runs, it first creates our Database.</p><pre>using (var scope = app.Services.CreateScope())<br>{<br>    var db = scope.ServiceProvider.GetRequiredService&lt;DockerComposeDemoDbContext&gt;();<br>    db.Database.Migrate();<br>}<br>app.Run();</pre><h3>Docker-Compose File</h3><p>Docker-Compose files allow us to build our Dockerfiles by specifying the build context and Dockerfile path, too. In our example, we use the image from the registry. I add it to the Docker hub; you can check the repository here <a href="https://hub.docker.com/r/aliyildizoz909/docker-demo">aliyildizoz909/docker-demo</a>.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fe6436e1bcd6b057a91ffb4d311c3a86/href">https://medium.com/media/fe6436e1bcd6b057a91ffb4d311c3a86/href</a></iframe><p>We have two services here: the first one configures our web API, and the second one configures the SQL server.</p><h4>docker-demo-web-api service</h4><pre> docker-demo-web-api:<br>    container_name: docker-demo-web-api-container<br>    image: aliyildizoz909/docker-demo<br>    environment:<br>      - MSSQL_SA_PASSWORD=Password1*<br>    ports:<br>      - &quot;5000:8080&quot;<br>    depends_on:<br>      - docker-demo-sqlserver    </pre><ul><li><strong>container_name: docker-demo-web-api-container</strong> : We set the container name as docker-demo-web-api-container</li><li><strong>image: aliyildizoz909/docker-demo</strong> : We specify the image source as <a href="https://hub.docker.com/r/aliyildizoz909/docker-demo">aliyildizoz909/docker-demo</a></li><li><strong>MSSQL_SA_PASSWORD=Password1*</strong> : Under the environment section, we set the MSSQL_SA_PASSWORD environment variable. This variable’s value and the variable&#39;s value with the same name under docker-demo-sql-server service should be the same. Otherwise, we cannot access the SQL server in the container. The variable&#39;s name is another important thing we should be careful about. We must use the same variable name when we get the password in our application.</li><li><strong>&quot;5000:8080&quot; : </strong>Here, we did a port publish. When we go to the <strong>5000 </strong>port via localhost, we tell the docker to redirect the requests to the <strong>8080 </strong>port in the docker-demo-web-apicontainer. <strong>8080 </strong>port is the default port of our published app in the container for asp.net core apps. You can change it via the ASPNETCORE_URLS environment variable or with the new one ASPNETCORE_HTTP_PORTS . <a href="https://learn.microsoft.com/en-us/dotnet/core/compatibility/containers/8.0/aspnet-port">See more information</a>.</li><li><strong>depends_on </strong>This section defines the priority of services to run. We add here which services we want to run before the current service. So, in our example, we add docker-demo-sqlservice here to run our application after the SQL server is ready.</li></ul><h4>docker-demo-sqlserver service</h4><pre>  docker-demo-sqlserver:<br>    container_name: docker-demo-sqlserver-container<br>    image: mcr.microsoft.com/mssql/server:2022-latest<br>    environment:<br>      - ACCEPT_EULA=Y<br>      - MSSQL_SA_PASSWORD=Password1*<br>    ports:<br>      - &quot;1433:1433&quot;</pre><ul><li><strong>container_name: docker-demo-sqlserver-container</strong> : We set the container name as docker-demo-sqlserver-container</li><li><strong>image: mcr.microsoft.com/mssql/server:2022-latest</strong> : We specify the image source as <a href="https://hub.docker.com/_/microsoft-mssql-server">mcr.microsoft.com/mssql/server:2022-latest</a></li><li>ACCEPT_EULA and MSSQL_SA_PASSWORD are mandatory environment variables for this image. <strong>ACCEPT_EULA=Y</strong> means that we confirm acceptance of the <a href="https://go.microsoft.com/fwlink/?linkid=857698">End-User Licensing Agreement</a>. <strong>MSSQL_SA_PASSWORD </strong>environment variable sets the password for the database system administrator (user-id = ‘sa’) to connect to the SQL Server once the container runs.</li><li><strong>&quot;1433:1433&quot; </strong>1433 port is the default port for the SQL server. So here, we mapped the host port number(1433) and the container port number(1433).</li></ul><h3>Running Docker-Compose File</h3><p>With the following command, we run our docker-compose file.</p><pre>docker compose up</pre><p>After running this command, we access our application here <a href="http://localhost:5000/swagger/index.html">http://localhost:5000</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*H4HlQi4jOYSehby-x-v6dw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4Qy76jZgWAR74vm06so7Yw.png" /><figcaption><a href="http://localhost:5000/swagger/index.html">http://localhost:5000/swagger/index.html</a></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hOzFYqhnw5AEN3lEkU-1ng.png" /></figure><p>You can also check my blog <a href="https://medium.com/p/a523233bb9a4/edit?source=your_stories_page-------------------------------------">Understanding Asp.Net Core Dockerfile</a> if you want to understand how the Dockerfile works and the Dockerfile of this image(<a href="https://hub.docker.com/r/aliyildizoz909/docker-demo">aliyildizoz909/docker-demo</a>).</p><p>Thank you.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4ee8317d3b87" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Understanding Asp.Net Core Dockerfile]]></title>
            <link>https://medium.com/@aliyildizoz/understanding-asp-net-core-dockerfile-a523233bb9a4?source=rss-a62137e94030------2</link>
            <guid isPermaLink="false">https://medium.com/p/a523233bb9a4</guid>
            <category><![CDATA[docker-build]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[aspnetcore]]></category>
            <category><![CDATA[dockerfiles]]></category>
            <category><![CDATA[net-core]]></category>
            <dc:creator><![CDATA[Ali Yıldızöz]]></dc:creator>
            <pubDate>Tue, 23 Apr 2024 01:34:47 GMT</pubDate>
            <atom:updated>2024-04-23T01:34:47.066Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*735l_c22kUuwM6jZ.png" /></figure><h3>Dockerfile Build Process</h3><p>A Dockerfile is a file that contains a set of instructions listed sequentially. In other words, it is a set of instructions. Docker reads and builds these files to create images and later uses these images to create containers.</p><h4>But how it works?</h4><p>When we build a Dockerfile, Docker starts many processes from the first line down to the end of the lines. Every line corresponds to an image layer, using the previous layer as a base image. Docker also caches every layer in this process to avoid building the unchanged layers again. With this cache mechanism, Docker reduces the building time.</p><h4>Docker build steps</h4><ol><li>In the first step, Docker reads the first line and pulls(if it does not exist) the specified base image from a registry during the FROM instruction process.</li><li>If there are executions of instructions in the Dockerfile, such as RUN, COPY or ADD Docker creates a temporary container from the previous image layer and executes these instructions in this container. After it completes the executions, Docker captures the changes in the temporary container and creates a new image&#39;s layer from these changes. After executions of instructions the layer will be created and Docker removes these temporary containers.</li><li>If a multi-stage build is involved and the stages don’t depend on each other, Docker builds every stage in parallel.</li><li>After Docker creates layers for each line, it tags these layers with a final image.</li></ol><h4>Importance of Instruction Order</h4><p>Due to the caching mechanism, the order of instructions becomes very important. When creating a Dockerfile, we should first add the instructions that are less likely to change.</p><p>For example, we have a Dockerfile below.</p><pre>FROM scratch<br>COPY src .<br>EXPOSE 80</pre><p>We had built this file, and Docker started to put every line into the cache. Later, we needed to change our source files and rebuild the image. In this case, docker has been building COPY src . and EXPOSE 80 lines but does not build FROM scratchline; it gets it from the cache. This happens because the changes happen after FROM instruction, they don&#39;t affect this layer.</p><p>If we change the Dockerfile below</p><pre>FROM scratch<br>EXPOSE 80<br>COPY src .</pre><p>Even if we change src files, Docker would not rebuild the EXPOSE 80 line. It uses the cached layer because the changes happen after this line.</p><p>After understanding how Docker builds a Dockerfile, we can review every Asp.Net Core Web API Dockerfile instruction.</p><h3>Asp.Net Core Web Api <strong>Dockerfile</strong></h3><p>VS 2022 has a Docker support tool that helps us create and build Dockerfiles or run containers. This is the default Dockerfile with recommended instructions provided by the VS 2022.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0af13b3da6fa3834de6ee1d48eda6eb2/href">https://medium.com/media/0af13b3da6fa3834de6ee1d48eda6eb2/href</a></iframe><pre>1. <strong>FROM</strong> mcr.microsoft.com/dotnet/aspnet:8.0 <strong>AS </strong><em>base</em></pre><p>We pulled an image with the FROM instruction and gave it a temporary name(base). This image is a Debian OS image that includes the Asp.net Core 8.0 runtime. You can see its <a href="https://github.com/dotnet/dotnet-docker/blob/9ffd75ed9a9a8790e7c7af5c925eefe7e4015bc1/src/aspnet/8.0/bookworm-slim/amd64/Dockerfile">Dockerfile here</a>.</p><p><strong>Why do we add an image as a base here?</strong></p><p>To run an application, we should have an OS that has our application’s dependencies. Without a base image, we can’t run our applications.</p><pre>2. <strong>USER</strong> app</pre><p>We switch to an <strong>app</strong> user here. After this instruction, our OS changes the default user(<strong>root</strong>) to the <strong>app</strong> user. The app user has fewer permissions than the root user, which is better than using a limitless permission user for potential security vulnerabilities. <a href="https://learn.microsoft.com/en-us/dotnet/core/compatibility/containers/8.0/app-user">See more information</a>.</p><pre>3. <strong>WORKDIR </strong>/app</pre><p>With this instruction, we switched to the <strong>/app</strong> directory. If <strong>/app</strong> doesn’t exist, Docker creates it. This directory contains our published application files.</p><pre>base-stage<br>└── app</pre><p>The folder view in the image after creating the working directory at the base stage.</p><pre>4. <strong>EXPOSE </strong>8080<br>5. <strong>EXPOSE </strong>8081</pre><p>Here, we specify to docker that our app uses the 8080 and 8081 ports. This is not a port publish; do not get it wrong. Our application uses these ports between other containers that connect to it, not outside the container. If you want to access the ports from outside the container, you should make a port publish.</p><pre>7. <strong>FROM </strong>mcr.microsoft.com/dotnet/sdk:8.0 <strong>AS </strong>build</pre><p>We can use multi-stage builds in Dockerfiles. In this Dockerfile, we have 4 stages; every stage has a name and some operations to prepare the final stage. We use it to build multiple operations simultaneously, reducing the building time and the image size.</p><p><strong>How do multi-stage builds reduce image size?</strong></p><p>To develop a .Net application, we first need an SDK(Software Development Kit). Without an SDK, we can’t publish or build our application. However, SDKs are large, and they increase image sizes too much if we consider that we only need a runtime to run our application(that’s why we used a runtime as a base image, not an SDK). At this point, multi-stage builds are very helpful in avoiding unnecessary tools and SDKs. After a stage is completed, we move the files created by SDK to the final stage. Docker doesn’t move the SDK or the tool we installed in this stage to the final stage. So, in this way, our image only has a runtime and uses the SDK, too.</p><p>In this line, we create a stage called <strong>build</strong> from .Net SDK Image. In this stage, we perform our application’s build operations.</p><pre>8. <strong>ARG </strong>BUILD_CONFIGURATION=Release</pre><p>In this line, we declared an <strong>ARG </strong>called<strong> </strong>BUILD_CONFIGURATION with default value(Release). We will use this arg to change the build and publish configuration when we build Dockerfile.</p><p>Dockerfile lets us change the build behavior at runtime when we execute the build command. For example, we have two versions of our base image, and we want to build and publish our app with these two versions.</p><p><strong>What can we do here?</strong></p><p>First, we use two different Dockerfiles and add both versions to them. Yes, it works for us, but we have to build these images separately. However, if we use an arg like this ARG VERSION then, if we add the arg with <strong>$</strong> in our base image statement like this FROM base-image:$VERSION we can change the version when building the Dockerfile with --build-arg flag.</p><p>example: docker build --build-arg VERSION=1.0 -t my-app:1.0</p><pre>9. <strong>WORKDIR </strong>/src</pre><p>We switch to the <strong>/src</strong> directory in the <strong>build</strong> stage. Every stage has its own file system based on the image of the stage. So this is the directory, and the <strong>/app</strong> directory in the 3. line are in the different file systems.</p><pre>build-stage<br><strong>└── src</strong></pre><p>The folder view in the image after creating a working directory at the <strong>build </strong>stage.</p><pre>10. <strong>COPY</strong> [“DockerDemoWebApi/DockerDemoWebApi.csproj”,  “DockerDemoWebApi/”]</pre><p>COPY is the most necessary instruction in a Dockerfile; if we cannot copy our application to the image, what is the meaning of dockerizing the app? In this line, we copy our app file with the .csproj extension to the DockerDemoWebApi directory in the <strong>/src</strong> file. When we use the COPY instruction, Docker creates a directory even if it doesn&#39;t exist in the image, like the WORKDIR instruction.</p><p>The important thing about the COPY instruction is to consider which path to use when executing the docker build command. If we provide the wrong build context to the command, Docker does not find the files we want to copy and give an error.</p><p><strong>Why we only copied DockerDemoWebApi.csproj file?</strong></p><p>Before building the application, we should install its dependencies, such as the packages, libraries, or frameworks it uses. If docker can’t install a package, it stops the build process, allowing us to catch errors easily. Another reason we do this is to benefit from the cache mechanism. Once we install all packages successfully, Docker does not install the packages even if we rebuild the Dockerfile again. It uses this layer from the cache.</p><pre>build-stage<br><strong>└── src</strong><br>    └── DockerDemoWebApi<br>        └── DockerDemoWebApi.csproj</pre><p>The folder view in the image after copying DockerDemoWebApi.csproj file at the <strong>build </strong>stage. <em>We are still in the </em><strong><em>src </em></strong><em>directory.</em></p><pre>11. <strong>RUN </strong>dotnet restore “./DockerDemoWebApi/DockerDemoWebApi.csproj”</pre><p>We use RUN instructions to execute specified commands. In this line, we execute the dotnet restore command. This command creates config files to install our packages for the provided project. (./DockerDemoWebApi/DockerDemoWebApi.csproj).</p><pre>build-stage<br><strong>└── src</strong><br>    └── DockerDemoWebApi<br>        ├── obj<br>        │   ├── DockerDemoWebApi.csproj.nuget.dgspec.json<br>        │   ├── DockerDemoWebApi.csproj.nuget.g.props<br>        │   ├── DockerDemoWebApi.csproj.nuget.g.targets<br>        │   ├── project.assets.json<br>        │   └── project.nuget.cache<br>        └── DockerDemoWebApi.csproj</pre><p>The folder view in the image after copying DockerDemoWebApi.csproj file at the <strong>build </strong>stage.</p><pre>12. <strong>COPY </strong>. .</pre><p>We copy everything from the <strong>build context</strong> to the <strong>src</strong> directory in this line.</p><pre>build-stage<br><strong>└── src</strong><br>    └── DockerDemoWebApi<br>        ├── Controllers<br>        │   └── WeatherForecastController.cs<br>        ├── obj<br>        │   ├── DockerDemoWebApi.csproj.nuget.dgspec.json<br>        │   ├── DockerDemoWebApi.csproj.nuget.g.props<br>        │   ├── DockerDemoWebApi.csproj.nuget.g.targets<br>        │   ├── project.assets.json<br>        │   └── project.nuget.cache<br>        ├── Properties<br>        │   └── launchSettings.json<br>        ├── appsettings.Development.json<br>        ├── appsettings.json<br>        ├── DockerDemoWebApi.csproj<br>        ├── Program<br>        └── WeatherForecast.cs</pre><p>The folder view in the image after copying all files at the <strong>build </strong>stage.</p><h4>.dockerignore</h4><p>Our project has many folders and files. Most of them were created during development. These files make the development process easier and speed up the building process. However, we don’t need them in the image. Firstly, we start building the application in the image(dotnet build), and also, we don&#39;t develop it in the image. To avoid these files, we use the .dockerignore file. This also increases the building time when we build the Dockerfile.</p><pre>**/.dockerignore<br>**/.env<br>**/.git<br>**/.gitignore<br>**/.vs<br>**/.vscode<br>**/bin<br>**/docker-compose*<br>**/Dockerfile*<br>**/node_modules<br>**/obj<br>LICENSE<br>README.md<br>!**/.gitignore<br>!.git/HEAD<br>!.git/config<br>!.git/packed-refs<br>!.git/refs/heads/**</pre><pre>13. <strong>WORKDIR </strong>“/src/DockerDemoWebApi”</pre><p>We changed the working directory to build our application.</p><pre>14. <strong>RUN</strong> dotnet build “./DockerDemoWebApi.csproj” -c $BUILD_CONFIGURATION -o /app/build</pre><p>Here, we build our application with dotnet build command. To build our application with this command, we should give the application path where the files with the <strong>.csproj</strong> or <strong>.sln</strong> extension exist. With -c flag, we indicate which configuration(Debug or Release) we build the application with. We used $BUILD_CONFIGURATION arg here. This means we can provide this value when we build Dockerfile. If we don&#39;t provide it, the Docker uses the default arg value(8th Line). With -o flag, we provide the path that dotnet saves the created files after the building process.</p><pre>build-stage<br>├── src<br><strong>│   └── DockerDemoWebApi</strong><br>│       ├── Controllers<br>│       │   └── WeatherForecastController.cs<br>│       ├── obj<br>│       │   ├── DockerDemoWebApi.csproj.nuget.dgspec.json<br>│       │   ├── DockerDemoWebApi.csproj.nuget.g.props<br>│       │   ├── DockerDemoWebApi.csproj.nuget.g.targets<br>│       │   ├── project.assets.json<br>│       │   └── project.nuget.cache<br>│       ├── Properties<br>│       │   └── launchSettings.json<br>│       ├── appsettings.Development.json<br>│       ├── appsettings.json<br>│       ├── DockerDemoWebApi.csproj<br>│       ├── Program<br>│       └── WeatherForecast.cs<br>└── app<br>    └── build<br>        ├── appsettings.Development.json<br>        ├── appsettings.json<br>        ├── DockerDemoWebApi.deps.json<br>        ├── DockerDemoWebApi.dll<br>        ├── DockerDemoWebApi.exe<br>        ├── DockerDemoWebApi.pdb<br>        ├── DockerDemoWebApi.runtimeconfig.json<br>        ├── Microsoft.OpenApi.dll<br>        ├── Swashbuckle.AspNetCore.Swagger.dll<br>        ├── Swashbuckle.AspNetCore.SwaggerGen.dll<br>        └── Swashbuckle.AspNetCore.SwaggerUI.dll</pre><p>The folder view in the image after running the build command at the <strong>build </strong>stage.</p><p>If you notice, the app folder is on the same level as the src folder, that is because we provide the build path with a starting slash(-o /app/build). This usage creates folders at the root level.</p><pre>16. <strong>FROM </strong>build <strong>AS </strong>publish</pre><p>Docker also allows us to create a stage from another stage like here(16th line). In this stage, we do our publishing operations.</p><pre>17. <strong>ARG</strong> BUILD_CONFIGURATION=Release</pre><p>You may ask why we defined the BUILD_CONFIGURATION arg again; can’t we use the previous one? I quote from the docker doc. for the answer.</p><blockquote>An ARG variable definition comes into effect from the line on which it is defined in the Dockerfile not from the argument&#39;s use on the command-line or elsewhere. Source: <a href="https://docs.docker.com/reference/dockerfile/#scope">Dockerfile reference | Args-Scope</a></blockquote><pre>18. <strong>RUN </strong>dotnet publish “./DockerDemoWebApi.csproj” -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false</pre><p>With dotnet publish command, we publish our app using the provided configuration. The only strange thing is /p:UseAppHost=false . This expression means set false value to the UseAppHost property (/p:). .Net applications have some settings for development and publishing operations. We can change these settings in the project file(.csproj), or we can change them while building or publishing the application with a command. Basically, the UseAppHost property controls whether or not a native executable(the .exe file) is created for deployment. In the container, we don&#39;t execute this file manually; even if we want to, we still cannot run it in a Linux OS. Therefore, this file is not necessary. Also, it increases the image size if we create it.</p><pre>publish-stage<br>├── src<br>│   └── DockerDemoWebApi<br>│       ├── Controllers<br>│       │   └── WeatherForecastController.cs<br>│       ├── obj<br>│       │   ├── DockerDemoWebApi.csproj.nuget.dgspec.json<br>│       │   ├── DockerDemoWebApi.csproj.nuget.g.props<br>│       │   ├── DockerDemoWebApi.csproj.nuget.g.targets<br>│       │   ├── project.assets.json<br>│       │   └── project.nuget.cache<br>│       ├── Properties<br>│       │   └── launchSettings.json<br>│       ├── appsettings.Development.json<br>│       ├── appsettings.json<br>│       ├── DockerDemoWebApi.csproj<br>│       ├── Program<br>│       └── WeatherForecast.cs<br>└── app<br>    ├── build<br>    │   ├── appsettings.Development.json<br>    │   ├── appsettings.json<br>    │   ├── DockerDemoWebApi.deps.json<br>    │   ├── DockerDemoWebApi.dll<br>    │   ├── DockerDemoWebApi.exe<br>    │   ├── DockerDemoWebApi.pdb<br>    │   ├── DockerDemoWebApi.runtimeconfig.json<br>    │   ├── Microsoft.OpenApi.dll<br>    │   ├── Swashbuckle.AspNetCore.Swagger.dll<br>    │   ├── Swashbuckle.AspNetCore.SwaggerGen.dll<br>    │   └── Swashbuckle.AspNetCore.SwaggerUI.dll<br>    └── publish<br>        ├── appsettings.json<br>        ├── DockerDemoWebApi.deps.json<br>        ├── DockerDemoWebApi.dll<br>        ├── DockerDemoWebApi.pdb<br>        ├── DockerDemoWebApi.runtimeconfig.json<br>        ├── Microsoft.OpenApi.dll<br>        ├── Swashbuckle.AspNetCore.Swagger.dll<br>        ├── Swashbuckle.AspNetCore.SwaggerGen.dll<br>        ├── Swashbuckle.AspNetCore.SwaggerUI.dll<br>        ├── web.config<br>        └── appsettings.Development.json</pre><p>The folder view in the image after running the publish command at the <strong>publish </strong>stage.</p><pre>20. <strong>FROM </strong>base <strong>AS </strong>final</pre><p>We create a stage named the <strong>final</strong> from the <strong>base</strong> stage.</p><pre>21. <strong>WORKDIR </strong>/app</pre><p>We change the working directory to <strong>/app</strong>.</p><pre>22. <strong>COPY —from</strong>=publish /app/publish .</pre><p>In the COPY instruction, we can also copy files from another stage or an image from a registry. To use COPY like that, we use --from flag. In this line, we copied all the files in the publish folder from the <strong>publish</strong> stage to the <strong>/app</strong> directory.</p><pre>final-stage<br><strong>└── app</strong><br>    ├── appsettings.json<br>    ├── DockerDemoWebApi.deps.json<br>    ├── DockerDemoWebApi.dll<br>    ├── DockerDemoWebApi.pdb<br>    ├── DockerDemoWebApi.runtimeconfig.json<br>    ├── Microsoft.OpenApi.dll<br>    ├── Swashbuckle.AspNetCore.Swagger.dll<br>    ├── Swashbuckle.AspNetCore.SwaggerGen.dll<br>    ├── Swashbuckle.AspNetCore.SwaggerUI.dll<br>    ├── web.config<br>    └── appsettings.Development.json</pre><p>The folder view in the image after copying the <strong>publish </strong>files at the <strong>final </strong>stage.</p><pre>23. <strong>ENTRYPOINT </strong>[“dotnet”, “DockerDemoWebApi.dll”]</pre><p>After making our application ready to run, we use ENTRYPOINT instructions for the running operation. Here, we tell Docker to run our application automatically when someone creates a container from the image created from this Dockerfile.</p><h3>Building Dockerfile</h3><p>I use the docker build command for this operation, but you can also build it with the VS 2022 Docker Support tool.</p><h4>Build Command Schema</h4><pre>docker build [OPTIONS] PATH | URL | -</pre><p>The first step in building a Dockerfile is specifying the build context. The built context means that when Docker starts to build the Dockerfile, it copies your files from which PATH or URL. In our Dockerfile, we copy every file from under the DockerDemoWebApi folder. Because of that, our built context has to be the parent folder of the DockerDemoWebApi folder. To understand more, in the <strong>10th line</strong>, we specify docker that our DockerDemoWebApi.csproj file is under the DockerDemoWebApi folder. So if I give a wrong build context to docker, it searches the DockerDemoWebApi folder, and because of the wrong build context, error occurs.</p><pre>docker build ./WrongBuildContext</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4RXKjdopRcusBpoIVayQKg.png" /><figcaption>docker build context error</figcaption></figure><p>The second step is specifying the Dockerfile path. This is also important because if your Dockerfile path is different from your build context, you may have to use -f flag to specify the Dockerfile path.</p><p>You can give a tag and a name to your image with -t flag, but if you don&#39;t use it, docker sets &lt;none&gt; for the name and tag. If you only use one word, docker sets that word as the name of the image and shows your current image tag as the latest. However, if you use a colon(:), docker sets the word on the left of the colon as a name and the right of the colon as a tag.</p><pre><strong>docker build -t</strong> docker-demo<strong>:</strong>v1 <strong>-f</strong> ./DockerDemoWebApi/Dockerfile . </pre><p>With this command, we tell docker that;</p><ol><li>Our Dockerfile is in the DockerDemoWebApi folder, so, search for it from the current path. -f ./DockerDemoWebApi/Dockerfile</li><li>Your build context is where I execute this command to .</li><li>Your image name is docker-demo and your tag is v1 .</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/687/1*M6dgyLsFu67neoZkyB9vVA.png" /><figcaption>after executing the build command</figcaption></figure><pre>$ docker image ls<br>REPOSITORY    TAG       IMAGE ID       CREATED             SIZE<br>docker-demo   v1        d798fa0b4c44   About an hour ago   221MB</pre><p>The blog took a bit longer, but I wanted you to understand better how Docker builds operations.</p><p>Thank you for reading until the end. See you.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a523233bb9a4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Capacitor ile Hibrit uygulama geliştirme]]></title>
            <link>https://medium.com/software-development-turkey/capacitor-ile-hibrit-uygulama-gelistirme-1b7425df3377?source=rss-a62137e94030------2</link>
            <guid isPermaLink="false">https://medium.com/p/1b7425df3377</guid>
            <category><![CDATA[hybrid-app-development]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[capacitorjs]]></category>
            <category><![CDATA[capacitor]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <dc:creator><![CDATA[Ali Yıldızöz]]></dc:creator>
            <pubDate>Sun, 14 Aug 2022 15:20:17 GMT</pubDate>
            <atom:updated>2022-08-15T05:49:45.810Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FgI8SbYam44JYlRtrt2cAA.png" /></figure><p>Merhabalar,</p><p>Bildiğiniz gibi mobil uygulama geliştirmek için bir çok yöntem var. Yaptığınız projeye, projenin süresine veya başka parametrelere bağlı olarak bu yöntemlerden birini seçebiliyoruz.</p><h4>Bu yöntemler;</h4><ul><li><strong>Native (ios: </strong>Objective-C, Swift <strong>android: </strong>Java, Kotlin<strong>)</strong></li><li><strong>Cross-Platform Native(</strong>Xamarin, React Native, Flutter<strong>)</strong></li><li><strong>Hybrid (</strong>Capacitor, Apache Cordova, Adobe PhoneGap<strong>)</strong></li><li><strong>PWA(</strong>Progressive Web Applications<strong>)(</strong>HTML, CSS, Javascript<strong>)</strong></li></ul><p>Ben bu yazımda sizlere <strong>Capacitor </strong>ile nasıl <strong>Hibrit </strong>uygulama geliştirilir, android ve ios’a nasıl uygulama çıkartılır, cli komutları nelerdir v.b. soruları cevaplamaya çalışacağım.</p><p>Teknik konulara geçmeden önce Hibrit Uygulama ve WebView nedir ve nasıl çalışır bunlara bakalım.</p><h3>Hibrit mobil uygulama nedir?</h3><p>Kısaca tek bir code base ile standart web teknolojilerini -Html, CSS ve javascript- kullanarak, bir çok mobil platformda çalışan uygulamalara hibrit uygulama diyebiliriz. Adının hibrit olmasının sebebi uygulamamızın aslında web uygulaması olduğu halde native bir şekilde kullanılabiliyor olmasından kaynaklanıyor.</p><h3>Hibrit uygulama nasıl çalışır?</h3><p>Hibrit uygulamalar mobil cihazda bir “web container” içinde çalıştırılır. Web Containerlar hibrit uygulamalarımız için bir browser runtime’ı sağlar. Bu sayede bir web uygulamamızı bir mobil cihazda çalıştırabiliyoruz.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nT5iXk8Njlzo4YqOibjQ6A.png" /><figcaption><a href="https://ionic.io/_next/image?url=https%3A%2F%2Fimages.prismic.io%2Fionicframeworkcom%2F2467bff206b39cd3bfa7fd8b8b0b91988d70485f_hybrid-app-development.png&amp;w=1920&amp;q=75">image (1440×720) (ionic.io)</a></figcaption></figure><p>Tabii web sayfasını sadece çalıştırarak ancak sayfaları görüntüleyebiliriz. Eğer cihazla da konuşmak istiyorsak -kameraya erişim gibi- bazı aracı teknolojiler kullanmamız gerekiyor.</p><h4>Hibrit Uygulama Geliştirme Araçları</h4><ul><li><strong>CapacitorJs</strong></li><li><strong>Apache Cordova</strong></li><li><strong>Adobe PhoneGap</strong></li><li><strong>Ionic Framework( </strong>Bir UI Framework’üdür Capacitor ile kullanılır<strong>)</strong></li></ul><h3>Capacitor Nedir?</h3><p>Şu anda <a href="https://capacitorjs.com/docs/updating/4-0">v4.0&#39;ı</a> çıkarmış olan Capacitor, Ionic tarafından geliştirilmiş Ionic Framework, popüler web teknolojileri(Angular, React, Vue v.b.) ya da custom bir web sayfası ile de kullanılabilen bir hibrit uygulama geliştirme aracıdır. Kendi web sayfasında kendini “hibrit uygulamaların bir sonraki evrimi” olarak tanıtıyor. Aynı zamanda Apache Cordova ve Adobe PhoneGap’in manevi halefi olduğunu da belirtiyor. Bu nedenle bir çok Cordova plugini ile de geriye dönük uyumlu çalışabiliyor. Capacitor ayrıca PWA(Progressive Web Applications)’ı da destekliyor. Yani capacitor pluginleri PWA uygulamasında da sorunsuz çalışabiliyor.</p><h4>Capacitor Plugin nedir?</h4><p>Capacitor web sayfalarımızı göstermekle beraber cihaz ile web uygulamamız arasında köprü görevi görüyor. Eğer telefonun bir özelliğine erişmek istiyorsak onunla ilgili plugini web uygulamamıza eklememiz gerekiyor. Örneğin kameraya veya galeriye erişim için <a href="https://capacitorjs.com/docs/apis/camera">camera </a>plugini’ni ya da pilin yüzde kaç kaldığını, şuan şarj ediliyor mu gibi bilgileri almak için <a href="https://capacitorjs.com/docs/apis/device">device </a>plugini’ni web projemize eklemek gerekiyor. Bu pluginleri npm ile projenize ekleyebilirsiniz.</p><p>Capacitor Community’si tarafından geliştirilen <a href="https://github.com/capacitor-community/electron"><strong>electron</strong></a><strong> </strong>plugini ile de uygulamalarınızı desktop app olarak Linux, Mac ve Windows’ta çalıştırabilirsiniz.</p><h4>Capacitor Pluginleri</h4><ul><li><a href="https://capacitorjs.com/docs/apis"><strong>Resmi Pluginler</strong></a><strong>: </strong>Capastior takımı tarafından maintain edilen pluginlerdir. (<a href="https://capacitorjs.com/docs/apis/action-sheet">Action Sheet</a>, <a href="https://capacitorjs.com/docs/apis/app">App</a>, <a href="https://capacitorjs.com/docs/apis/camera">Camera</a>, <a href="https://capacitorjs.com/docs/apis/google-maps">Google Maps</a> v.b.)</li><li><a href="https://capacitorjs.com/docs/plugins/community"><strong>Community Plugins</strong></a><strong>: </strong>Capacitor Community’si tarafından maintain edilen pluginlerdir. <a href="https://github.com/capacitor-community">Buradan</a> açık kaynak pluginlere bakabilirsiniz.</li></ul><p>Tabii native kadar cihazın tüm özelliklerine erişemesek de çoğu özelliğine erişebiliyoruz. Eğer bu pluginler işinizi görmüyorsa kendi plugin’inizi de geliştirip yayınlayabilirsiniz. Nasıl geliştireceğinize <a href="https://capacitorjs.com/docs/plugins/creating-plugins">buradan </a>bakabilirsiniz.</p><h3>Capacitor Kurulum</h3><p>Kurulum için gereksinimler aşağıda belirtilmiştir.</p><ul><li>Nodejs versiyonu 14 veya üzeri</li><li><strong>Android için<br></strong>– Android Studio<br>– Android SDK Kurulumu</li><li><strong>iOS için<br></strong>– Xcode<br>– Xcode Command Line Tools<br>– Homebrew<br>– Cocoapods</li></ul><h4>Kurulum</h4><ul><li>Öncelikle eğer npm’i kurduysanız aşağıdaki komutu çalıştırın sonra aşağıda uygulamanız için gerekli olan soruları cevaplayın.<br>npm init @capacitor/app</li><li>Uygulamanın adını<br>? What is the name of your app?</li><li>Uygulamanın kurulacağı dosya yolu<br>? What directory should be used for your app?</li><li>Uygulama ID’si (ios için ak Bundle Id ve Android için Application Id’ye karşılık gelen benzersiz domain adı)<br>? What should be the App ID for your app?</li><li>Bu işlemlerden sonra verdiğimiz dosya yolunda bazı dosyalar oluşacaktır. Eğer <strong>node_modules </strong>klasörü yoksa oluşturmak için uygulama klasöründe aşağıdaki kodu çalıştırın. Ayrıca iki tane capacitor plugini(camera ve splash-screen) de default olarak gelecektir.<br>npm install</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/226/1*UoOqOaorymS-C663F8HSpg.png" /><figcaption>Uygulama dosyasının son hali</figcaption></figure><h4>capacitor.config.json</h4><p>Capacitor, projemiz oluştuktan sonra <strong>capacitor.config.json </strong>adında bir dosyayı da oluşturdu. Bu dosyada aslında cevapladığımız bilgilerin olduğunu görebilirsiniz. Capacitor mobil uygulamamızı çalıştırırken bu dosyada ki bilgileri kullanıyor. Uygulamayla ve Pluginlerle ilgili yada genel olarak uygulamayı geliştirme açısından bazı ayarları burada yönetiyoruz.</p><pre>{<br>   &quot;appId&quot;: &quot;com.example.app&quot;, <br>   &quot;appName&quot;: &quot;capacitor-demo&quot;,<br>   &quot;bundledWebRuntime&quot;: false,<br>   &quot;webDir&quot;: &quot;dist&quot;,<br>   &quot;plugins&quot;: {<br>      &quot;SplashScreen&quot;: {<br>          &quot;launchShowDuration&quot;: 0<br>      }<br>   }<br>}</pre><p>Bu dosyada dikkat etmemiz gereken en önemli alan <strong>webDir </strong>dir. Capacitor mobil uygulamayı çıkartırken her zaman projemizin build alınmış ve içinde index.html dosyasının olduğu bir klasör yolu ister. Bu yolu yanlış verirsek mobil uygulamayı çıkartamayabilir. İşte bu yolu capacitor’e söylediğimiz yer “webDir”. Dosya da yer alan diğer propertylere <a href="https://capacitorjs.com/docs/config#schema">buradan</a> bakabilirsiniz.</p><h4>manifest.json</h4><p>Capacitor’un oluşturduğu bir diğer dosya da <strong>manifest.json </strong>dosyasıdır. Bu dosya, PWA için uygulamamız hakkında bazı bilgiler içerir. Tabii sadece bu dosya PWA uygulaması oluşturmak için yeterli değildir. Çok daha farklı bir konu olduğu için, <a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps">buradan</a> nasıl oluşturacağınızla ilgili daha detaylı bilgilere ulaşabilirsiniz.</p><p>Web uygulamamızı ayağa kaldırmak için npm start komutunu uygulama klasöründe çalıştıralım.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*g-rx3iQIci4g0IJRUeRXgA.png" /><figcaption>Web uygulamamız çalışırken</figcaption></figure><p><em>Bir de söylemeye gerek duymadım ama uygulamamız mobilde çalışacağı için </em><strong><em>responsive </em></strong><em>bir şekilde geliştirme yapmak çok önem kazanıyor.</em></p><h4>iOS ve Android için CLI komutları</h4><p>Web uygulamamızın mobil platformlarda ki projelerini çıkartmak ve yönetmek için capacitor’un cli komutlarını kullanacağız.<br>Bu komutları kullanmak için uygulama klasörümüzde<br>npm i @capacitor/cli komutunu çalıştırıp ilgili paketi yükleyelim.</p><h4>Android ve iOS npm paketleri</h4><p>Android için npm i @capacitor/android komutunu,<br>iOS için npm i @capacitor/ios komutunu çalıştırın.</p><h4>Android platformu için uygulama çıkarma komutu</h4><p>Aşağıdaki komut ile <strong>android </strong>adında, android uygulamasının bulunduğu bir klasör oluşturuyoruz.<br>npx cap add android</p><h4>iOS platformu için uygulama çıkarma komutu</h4><p>Aşağıdaki komut ile <strong>ios </strong>adında, ios uygulamasının bulunduğu bir klasör oluşturuyoruz.<br>npx cap add ios</p><h4>Web uygulamasının build alınmış son halini çıkartma komutu</h4><p>Aşağıdaki komut ile <strong>dist </strong>adında, web uygulamasının build alınmış son halinin bulunduğu bir klasör oluşturuyoruz.<br>npm run-script build</p><p>Bu komutları uygulama klasöründe çalıştırın. Aşağıdaki görselde projenin son halini görebilirsiniz.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/245/1*cLsk-c05GVsNMLhm9JtzZQ.png" /><figcaption>uygulamaların bulunduğu <strong>android, ios </strong>ve <strong>dist </strong>klasörleri</figcaption></figure><p>Bu aşamadan sonra npx cap sync<em> </em>komutunu çalıştırıyoruz.<em> </em>Ayrıca bu komutu<em> </em>uygulamanızda yaptığınız her değişiklik veya eklenen her plugin için de tekrar çalıştırmamız gerekiyor. Bunu web uygulamamızdaki değişiklikler mobil uygulamalarımıza da yansısın diye yapıyoruz.</p><p>Android ve iOS klasörlerimiz oluştuysa bunları ister capacitor’un cli komutlarıyla isterseniz de kendi ide’lerinde(Xcode, Android Studio) açma veya çalıştırma işlemlerini yapabilirsiniz.</p><ul><li>npx cap open android/ios <strong>android </strong>veya <strong>ios </strong>klasörlerini kendi idelerinde açar (android için Android Studio ios için Xcode’u)</li><li>npx cap run android/ios Android Studio veya Xcode’u çalıştırır.</li></ul><h3>Avantajlar</h3><ul><li>Tek bir code base üzerinden geliştirme yapılabilmesi</li><li>Open Source olması</li><li>Eğer web developer iseniz yabancı olmadığınız bir ortamda kod yazma</li><li>Hızlı geliştirme, aynı anda iki platforma da uygulama çıkartma</li><li>Plugin’lerin PWA uygulamasında çalışabilmesi</li><li><a href="https://github.com/capacitor-community/electron">Electron</a> plugini ile bir çok işletim sisteminde çalışan desktop uygulaması geliştirme</li></ul><h3>Dezavantajlar</h3><ul><li>Web componentler kullanıldığı için native uygulama hissi vermemesi(UI,UX).</li><li>Her şeyi yapamama. Eğer 3D oyunlar gibi yüksek performans isteyen uygulamalar geliştiriyorsanız capacitor bunun için uygun olmayabilir.</li><li>Pluginlere bağımlı uygulamalar geliştirme. Eğer mevcut pluginler işinizi görmüyorsa kendi plugininizi yazmak zorunda kalmanız.</li></ul><p>Teşekkürler.</p><h4><strong>Kaynaklar</strong></h4><ul><li><a href="https://capacitorjs.com/">Capacitor by Ionic — Cross-platform apps with web technology (capacitorjs.com)</a></li><li><a href="https://ionic.io/resources/articles/what-is-hybrid-app-development">What is Hybrid Mobile App Development: Hybrid vs Native vs Web (ionic.io)</a></li><li><a href="https://aws.amazon.com/tr/mobile/mobile-application-development/">Mobile Application Development (amazon.com)</a></li><li><a href="https://f.hubspotusercontent00.net/hubfs/3776657/Capacitor%20Whitepaper.pdf">Capacitor Whitepaper.pdf (hubspotusercontent00.net)</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1b7425df3377" width="1" height="1" alt=""><hr><p><a href="https://medium.com/software-development-turkey/capacitor-ile-hibrit-uygulama-gelistirme-1b7425df3377">Capacitor ile Hibrit uygulama geliştirme</a> was originally published in <a href="https://medium.com/software-development-turkey">SDTR</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Active Record Pattern Nedir ?]]></title>
            <link>https://medium.com/software-development-turkey/active-record-pattern-nedir-46bcf33c8b91?source=rss-a62137e94030------2</link>
            <guid isPermaLink="false">https://medium.com/p/46bcf33c8b91</guid>
            <category><![CDATA[active-record-pattern]]></category>
            <category><![CDATA[architectural-patterns]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[activerecord]]></category>
            <dc:creator><![CDATA[Ali Yıldızöz]]></dc:creator>
            <pubDate>Wed, 23 Mar 2022 18:46:40 GMT</pubDate>
            <atom:updated>2024-04-14T23:06:03.664Z</atom:updated>
            <content:encoded><![CDATA[<h3>Active Record Pattern Nedir?</h3><p>Merhaba arkadaşlar,</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/957/1*HCGJxyRx3Z6lLZAL-g5bNg.jpeg" /></figure><p>Bu yazımda, Martin Fowler’ın 2003 yılında yayınlanan <em>Patterns of Enterprise Application Architecture </em>kitabında, ismini verdiği Active Record tasarım kalıbını inceleyeceğiz.</p><p>Değineceğim başlıklar aşağıda belirtilmiştir.</p><ul><li><strong>Data Source Architectural Patterns(</strong>Veri Kaynağı Mimarisi Kalıpları<strong>) nedir?</strong></li><li><strong>Active Record Pattern nedir?</strong></li><li><strong>Ne zaman kullanılır?</strong></li><li><strong>İmplementasyon</strong></li><li><strong>Avantajları ve Dezavantajları</strong></li></ul><h3><strong>Data Source Architectural Patterns nedir?</strong></h3><p>Active Record Pattern’inin de dahil olduğu bir tasarım kalıbı çeşididir. Bu çeşide dahil olan tasarım kalıpları, veri erişimini ve veri tabanı ile ilgili işlemleri kolaylaştırmayı amaçlamaktadır. Çoğu, veri tabanını saran bir obje oluşturarak bu işlemleri yapar.</p><p>Aşağıda bu gruba dahil olan tasarım kalıpları ve Martin Fowler tarafından yapılan kısa açıklamaları yer alıyor.</p><ul><li><strong>Table Data Gateway</strong></li></ul><blockquote>An object that acts as a Gateway to a database table. One instance handles all the rows in the table.</blockquote><ul><li><strong>Row Data Gateway</strong></li></ul><blockquote>An object that acts as a Gateway to a single record in a data <br>source. There is one instance per row.</blockquote><ul><li><strong>Active Record</strong></li></ul><blockquote>An object that wraps a row in a database table or view, encapsulates <br>the database access, and adds domain logic on that data.</blockquote><ul><li><strong>Data Mapper</strong></li></ul><blockquote>A layer of Mappers that moves data between objects and a database while keeping them independent of each other and the mapper itself.</blockquote><h3><strong>Active Record Pattern nedir?</strong></h3><p>Bu patternde de, diğer orm araçlarından aşina olduğumuz gibi veri tabanındaki her bir satır, kodlama tarafında bir entity’e denk gelir. Diğerlerinden ayrıldığı nokta, bu entity üzerinde aynı zamanda temsil ettiği tabloya ait tüm operasyonların (CRUD) yapılmasını sağlayan fonksiyonların da yer almasıdır.</p><p>Bu tür entityl’ere <strong>active record</strong> denir. Her active record, veri tabanına kaydetme ve yüklemeden ayrıca veriler üzerinde işlem yapan business logic’ten de sorumludur. Active record patter’nin en belirgin özelliği data access logic’i business nesnesinin içine yerleştirmesidir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/302/1*HzmpYpeCwUm889jsQBu1Bw.png" /><figcaption>Patterns of Enterprise Application Architecture(s.160)</figcaption></figure><p>Fotoğrafta, Person ismindeki bir tabloya ait CRUD operasyonları, business logic metotları ve aynı zamanda bir satıra karşılık gelen entity’nin bir arada olduğu görülebilir.</p><p>Active Record ve Row Data Gateway pattern’leri birlerine çok benzerdir. Aralarındaki temel fark, Row Data Gateway’in yalnızca veri tabanı erişimini içermesi, Aktive Record’un ise hem veri tabanı erişimi hem de business logic içermesidir.</p><h3><strong>Ne zaman kullanılır?</strong></h3><p>Active Record bazı yazılımcılar tarafından anti-pattern olarak isimlendiriliyor ancak kullanımının faydalı olduğu bazı durumlar ve projeler de olabilir.</p><p>Active Record tabanlı ORM’ler uygulamanızı çok hızlı bir şekilde oluşturmasını sağlar. Bu nedenle ucuz olması gereken ve kritik olmayan projeler için iyi bir çözüm olabilir. Bu tür projelere örnek olarak prototipler veya POC’lar(Proof Of Concept) verilebilir.</p><p>Active Record, business logic için çok karmaşık olmayan; okuma, yazma, güncelleme ve silme gibi işlemleri yapmamız için iyi bir seçim olabilir. Ana problem, ancak Active Record veri tabanındaki tabloya tam olarak karşılık gelirse iyi bir şekilde çalışır.</p><h3><strong>İmplementasyon</strong></h3><p><strong>Aşağıda bu patterni implement etmiş framework’ ler belirtilmiştir.</strong></p><ul><li>Eloquent (Laravel framework’ün bir parçası) — PHP</li><li>RoR ActiveRecord (Ruby on Rails framework’ün bir parçası) — Ruby</li><li>Django ORM (Django framework’ün bir parçası) — Python</li></ul><h4>Örnek</h4><p>Örneğimizde active record’umuz için veri tabanı işlemlerini ve verileri mapleme işini Dapper ile yapacağız. Basit bir şekilde CRUD işlemlerini yapan bir active record base sınıfı ve bir active record oluşturacağız. Bu süreçte yapacağımız işlemler, bize bu patternin avantajları ve dezavantajlarını daha iyi bir şekilde gösterecektir.</p><p>MsSql’de<strong> ActiveRecordPatternDb</strong> adında örnek bir veri tabanı ve şeması <strong>ActiveRecord </strong>olan <strong>Person </strong>adında da bir tablo oluşturdum.</p><p>Öncelikle birden fazla active record’umuzun olacağını göz önünde bulundurarak, her biri için ayrı ayrı, aynı fonksiyonları yazmak yerine ortak olan fonksiyonlar(Save, Update, Delete, Read gibi) için bir interface yazmak daha mantıklı geldi. Bir de base sınıfta sql komutu üreteceğimiz için tablonun bazı bilgilerini de(SchemaName) bu interface üzerinde tanımlamak işimizi daha da kolaylaştıracaktır.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ec09258a7433ef834ec5dbf6430f99a4/href">https://medium.com/media/ec09258a7433ef834ec5dbf6430f99a4/href</a></iframe><p>Yukarıda her tablo’da ortak olan metotların bulunduğu iki interface yazdım.</p><p>Aşağıda <strong>BaseActiveRecord </strong>adında generic olarak gelen active record’a göre sql komutları üreten bir sınıf oluşturdum. Bu sınıfı, <strong>Person </strong>active record’umuz kalıtım alacaktır.</p><p>Bu sınıfta, active record’tan sadece Schema Name ve tablosunun ismi(yani kendi ismi) dışında kalan tüm bilgiler reflection kütüphanesi kullanılarak alındı.</p><p>Aşağıda okuma işlemleri(<strong>Read </strong>ve <strong>FindById</strong>) diğerlerinden farklı olarak static bir şekilde tanımlanmış. Kitapta da query işlemlerini yapan metotlar static olarak verilmiş. Bunun sebebi, okuma işlemlerini yapmak için bir active record sınıfı instance’ı oluşturmak zorunda kalmamak içindir.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cb66e92a99260e3ce8665f8fa4e6afcf/href">https://medium.com/media/cb66e92a99260e3ce8665f8fa4e6afcf/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/01b09967e11f094a721b886031387737/href">https://medium.com/media/01b09967e11f094a721b886031387737/href</a></iframe><p>Yukarıda <strong>BaseActiveRecord’</strong>u<strong> </strong>ve <strong>IActiveRecord’</strong>u<strong> </strong>kalıtım alan <strong>Person </strong>isimli bir active recordumuz var. Görüldüğü gibi direkt kendisini base sınıfa parametre geçerek CRUD işlemlerini yapıyor.</p><p>Ben yazmadım ama, normalde business logic’ler de <strong>Person </strong>classına yazılır.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/61dd28802e50592f72de1dee44a32e79/href">https://medium.com/media/61dd28802e50592f72de1dee44a32e79/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/817/1*JQ7WCaOYONMU5hzzooOa4A.png" /><figcaption>Yukarıdaki kodun çıktısını</figcaption></figure><h3>Avantajları ve Dezavantajları</h3><h4>Avantajları</h4><ul><li>Hızlı uygulama geliştirme için iyi bir seçimdir.</li><li>Active record’ları oluşturmak ve anlamak kolaydır.</li><li>Kullanılıp atılan prototiplerin hızla uygulanmasını kolaylaştırır.</li><li>Bir active record’a bakarak direkt olarak veri tabanı tablosunun veri yapısını anlayabiliriz.</li><li>Çoğu ihitiyaç duyacağımız şeyleri aynı yerde kodladığımız için çok fazla katman oluşturmamıza gerek kalmaz. Bu da hızlı bir şekilde projeyi oluşturmamızı sağlar.</li></ul><h4>Dezavantajları</h4><ul><li>Martin Fowler’a göre eğer business logic’iniz karmaşıksa ve entityleriniz arasında bire çok gibi ilişkiler ve koleksiyonlar oluşturmak istiyorsanız, bunu Active Record ile yapmanız çok kolay değil. Bunun da <strong>Data Mapper Pattern’</strong>ini kullanmamıza yol açacağını söylüyor.</li><li>Bu pattern de nesne tasarımı ve veri tabanı tasarımı birleştirildiği için projenin ilerlemesi, her iki tarafında birlikte refactor edilmesini çok zorlaştırıyor.</li><li>İki katmanı birleştirdiği için bağımlılığın yüksek olması sonucu Unit Test yazmayı zorlaştırması.</li><li>SOLID prensiplerin S(Single Responsibility Principle) harfinin ihlal edilmesi. Her bir active record hem bir satıra denk gelen bir model hem bir data access objesi hem de business logic’leri üzerinde barındıran bir business objesi olması.</li></ul><p><strong>Robert C. Martin’in </strong>bu pattern ile ilgili düşünceleri…</p><blockquote>The problem I have with Active Record is that it creates confusion about these two very different styles of programming. A database table is a data structure. It has exposed data and no behavior. But an Active Record appears to be an object. It has “hidden” data, and exposed behavior. I put the word “hidden” in quotes because the data is, in fact, not hidden. Almost all ActiveRecord derivatives export the database columns through accessors and mutators. Indeed, the Active Record is meant to be used like a data structure.</blockquote><blockquote>On the other hand, many people put business rule methods in their Active Record classes; which makes them appear to be objects. This leads to a dilemma. On which side of the line does the Active Record really fall? Is it an object? Or is it a data structure?</blockquote><h4>Örneği hazırlarken yaşadığım deneyimler</h4><p>Eğer base bir sınıf oluşturup, Active Recordların yaptığı ortak işlemleri merkezileştirmek ve tekrar tekrar aynı kodu yazmak istemiyorsanız, en çok zorlanacağınız konular veri tabanı ve tablo ile ilgili, base sınıfın ihtiyaç duyacağı bilgileri ona ulaştırmanız olacaktır. Çoğu durumda ulaştıramayacağınız bilgileri reflection yardımı ile ulaşacaksınız. Bu bilgiler(tablonun şema adı, Primary Key adı, tablonun kendi adı vb) en başta active record’un üzerinde meta data olarak tutulursa işler biraz daha kolaylaşabilir.</p><p>Tablonun adı, örnekte direkt sınıfın adı olarak verildi bu sayede bu bilgiyi base sınıfa ulaştırmaya gerek olmadı benim için. Ama bu da active record’un adını tablo adı ile aynı olmasını her zaman zorunlu tutuyor. Zaten active record’un property’lerinin, tablosunun kolonlarına bire bir uyması gerektiğini söylemeye gerek yoktur. Çünkü base sınıfta generic tip ile çalıştığımız için ancak reflection yardımı ile property’leri okunabilir. Bunlardan biri yanlış olursa veri tabanına bağlanma ve tabloyu map etme kısımlarında hata çıkabilir. Zaten örnekte map’leme işlemlerini Dapper ile yaptık, eğer bu işlemler için de bir kütüphane kullanmak istemiyorsanız ayrıca bu işlemleri de kendiniz yapmanız gerekir.</p><p>Sonuç olarak active record’un veri tabanı ile çok sıkı bir ilişkisi olduğunu görebiliriz . Bir de ben her tablo’nun CRUD işlemleri için ayrı fonksiyonlar yazmak istemiyorum derseniz, belli bir yere kadar soyutlama yapıp sonrasında reflection kütüphanesi ile baya içli dışlı olmak gerekebilir. Bence bu pattern’i uygularken veri tabanının çok değişmemesi gerektiğini göz önünde bulundurarak en baştan her şeyin belirlenmesi ve Database First bir yaklaşım ile projeyi oluşturmaya başlamak işleri daha kolaylaştırabilir.</p><p>Demonun linkini <a href="https://github.com/AliYildizoz/ActiveRecordDemo">buraya bırakıyorum</a>. Teşekkürler.</p><h4>Kaynaklar</h4><ul><li><em>Patterns of Enterprise Application Architecture</em>, Martin Fowler</li><li><a href="https://www.geekyhacker.com/2019/05/25/my-thoughts-on-active-record-pattern/">https://www.geekyhacker.com/2019/05/25/my-thoughts-on-active-record-pattern/</a></li><li><a href="https://karoldabrowski.com/blog/active-record-pattern-or-anti-pattern-overview/">https://karoldabrowski.com/blog/active-record-pattern-or-anti-pattern-overview/</a></li><li><a href="https://softwareengineering.stackexchange.com/questions/119352/does-the-activerecord-pattern-follow-encourage-the-solid-design-principles">https://softwareengineering.stackexchange.com/questions/119352/does-the-activerecord-pattern-follow-encourage-the-solid-design-principles</a></li><li><a href="https://medium.com/oceanize-geeks/the-active-record-and-data-mappers-of-orm-pattern-eefb8262b7bb">The Active Record and Data Mappers of ORM Pattern | by Utpal Biswas | Cybridge Geeks | Medium</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=46bcf33c8b91" width="1" height="1" alt=""><hr><p><a href="https://medium.com/software-development-turkey/active-record-pattern-nedir-46bcf33c8b91">Active Record Pattern Nedir ?</a> was originally published in <a href="https://medium.com/software-development-turkey">SDTR</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[React’ta redux ile middleware kullanımı ve redux-thunk.]]></title>
            <link>https://medium.com/software-development-turkey/reactta-redux-ile-middleware-kullan%C4%B1m%C4%B1-ve-redux-thunk-d46445f7b9c6?source=rss-a62137e94030------2</link>
            <guid isPermaLink="false">https://medium.com/p/d46445f7b9c6</guid>
            <category><![CDATA[react]]></category>
            <category><![CDATA[redux-logger]]></category>
            <category><![CDATA[redux-middleware]]></category>
            <category><![CDATA[redux]]></category>
            <category><![CDATA[redux-thunk]]></category>
            <dc:creator><![CDATA[Ali Yıldızöz]]></dc:creator>
            <pubDate>Sun, 28 Jun 2020 08:49:16 GMT</pubDate>
            <atom:updated>2024-04-24T21:29:22.820Z</atom:updated>
            <content:encoded><![CDATA[<h3>React’ta redux ile middleware kullanımı ve redux-thunk</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Fqp-auY3RgSfrIAYFekoOQ.jpeg" /></figure><p>Merhabalar,</p><p>Bu yazımda Redux’a nasıl middleware eklenir , neden ihtiyaç vardır, redux-thunk ile api çağrılarımızı nasıl yaparız vb. sorulara cevaplamaya çalışacağım. <a href="https://medium.com/software-development-turkey/redux-nedir-ve-neden-kullan%C4%B1l%C4%B1r-68153ad3adfb">Redux’ı </a>veya <a href="https://medium.com/software-development-turkey/reactta-redux-nas%C4%B1l-ve-neden-kullan%C4%B1l%C4%B1r-f8e14ff1fdbc">React’ta redux</a>’ın kullanımı ile ilgili anlamadığınız bir nokta varsa önceki yazılarıma göz atmanızı öneririm.</p><h3>Middleware’e neden ihtiyacımız var ?</h3><p>Redux’ta bir kuralımız var. Reducer’larımız pure olmalı ve asla side effect yaratacak işlemleri barındırmamalıdır. Bazı durumlarda -hatta çoğu durumda- pure olmayan işlemler yapmak zorunda kalırız. Bu işlemler ; api istekleri, bazı javascript fonksiyonları(math.random, array.push) veya kendi uygulamamızın gerektirdikleri olabilir. İşte tam bu noktada middleware ihtiyacı ortaya çıkıyor. Tabii, bu middleware’ler sadece side effect işlemleri için yoklar. Aynı zamanda state’timizi takip etmek için bazı ekstra araçları da middleware olarak kullanabiliriz.</p><h4>Bu yazımda 2 tane middleware’den bahsedeceğim.</h4><ul><li><a href="https://github.com/reduxjs/redux-thunk">redux-thunk</a></li><li><a href="https://github.com/LogRocket/redux-logger">redux-logger</a></li></ul><h3>redux-thunk middleware</h3><p>Reducer’ın pure kalmasını sağlayan bir kütüphanedir. Side effect işlemlerini bu kütüphaneyi kullanarak yaparız. Çok basit bir işlem yapar. Normalde biz, dispatch kullanarak action’ı reducer’a parametre geçip state’i değiştirirdik. Bu kütüphane ile dispatch fonksiyonuna , action -yani obje - değilde yapacağımız işlemlerin olduğu bir fonksiyon göndeririz. Bu sırada thunk middleware’imiz, gelen parametrenin fonksiyon olup olmadığını kontrol edip fonksiyon ise çalıştırıyor. Fonksiyon içinde dispatch’e tekrar bir action gönderiliyor. Yine aynı şekilde thunk middleware araya girip dispatch’e gönderilen action’ın fonksiyon olup olmadığına bakıyor, eğer fonksiyonsa yine çalıştırıyor.Bu şekilde ,ta ki bir action -obje- yakalayana kadar devam eder. Objeyi yakaladığı an ,reducer’a bu objeyi yani action’ı parametre gönderiyor. Biraz karışık oldu biliyorum. Aşağıda ki flow üzerinden adımları tek tek anlatacağım.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HRgpAfEJR3uWGunBpnmqdQ.png" /><figcaption>redux-thunk data flow</figcaption></figure><ol><li>Dispatch fonksiyonuna action parametre olarak geçirilir.(action’ın bir fonksiyon olduğunu düşünelim.)</li><li>Thunk middleware araya girerek, action’ın fonksiyon olup olmadığına bakıyor.</li><li>Action’ımız fonksiyon olduğu için bu fonksiyona dispatch parametre gönderilip ,fonksiyonumuz, middleware içinde çalıştırılıyor. Fonksiyonumuz , api çağrısını veya ne işlem yapıyorsa o işlemi yapıp bir action-obje- yaratıyor. Parametre olarak gelen dispatch’e de ,yaratılan action parametre verip tekrar çalıştırıyor.</li><li>Dispatch tekrar çalıştırıldığı için thunk middleware’miz tekrar araya giriyor. Ve bu işlemler yine tekrarlanıyor.</li><li>Gelen action artık bir obje olduğu için normal yoluna devam ediyor. Yani reducer’a parametre geçiliyor.</li></ol><pre>function createThunkMiddleware(extraArgument) {<br>  return ({ dispatch, getState }) =&gt; next =&gt; action =&gt; {<br>    if (typeof action === &#39;function&#39;) {<br>      return action(dispatch, getState, extraArgument);<br>    }<br><br>    return next(action);<br>  };<br>}<br><br>const thunk = createThunkMiddleware();<br>thunk.withExtraArgument = createThunkMiddleware;<br><br>export default thunk;</pre><p>Yukarıdaki kısacık fonksiyon tüm işlemlerimizi yapıyor. Gelen action’ın tipine bakıp fonksiyon ise gerekli parametreleri verip çalıştırıyor, değilse sonraki adıma geçiyor. Fonksiyon olarak gönderdiğimiz action’a, dispatch ve yukarıdaki diğer değerler parametre gönderiliyor.</p><blockquote>Fonksiyonumuz <strong>dispatch’i </strong>kullanmak zorundadır. Aksi halde programımız ilerlemez.</blockquote><h4>Nasıl Kullanılır ?</h4><p>Projemize dahil etmek için aşağıdaki komutu çalıştırıyoruz.</p><pre>npm install redux-thunk</pre><p>Aşağıda middleware’lerimizi eklemek için redux’ın <strong>applyMiddleware </strong>fonksiyonundan yararlanıyoruz. createStore fonksiyonunun ikinci parametresine bu fonksiyonu göndererek middleware’lerimizi aktif hale getiriyoruz.</p><pre>import { createStore, applyMiddleware } from &#39;redux&#39;;<br>import thunk from &#39;redux-thunk&#39;;<br>import rootReducer from &#39;./reducers/index&#39;;<br><br>const store = createStore(rootReducer, applyMiddleware(thunk));</pre><h3>Örnek</h3><p>Önceki <a href="https://medium.com/software-development-turkey/reactta-redux-nas%C4%B1l-ve-neden-kullan%C4%B1l%C4%B1r-f8e14ff1fdbc">yazımda</a>,redux’ın react ile kullanımıyla ilgili bir <a href="https://github.com/AliYildizoz909/redux-example">örnek </a>yapmıştım. Redux-thunk’ı da bu örnek üzerinde kullanacağım.</p><p>Örneğimiz’de todo’ların başlangıç değerlerini api den çekeceğiz. Bunun için gerçek bir uygulama yapamayacağımızdan, sahte bir api kullanacağız. Böyle durumlarda ve uygulamalarımızı denemek için geliştirilen bir kütüphane var <strong>json-server. </strong>Bizde bu kütüphaneyi kullanacağız.</p><h4><strong>json-server kurulumu</strong></h4><pre>npm install -g json-server</pre><p>Yukarıdaki komutu çalıştırarak bilgisayarımıza kuruyoruz.</p><p>Bu işlemden sonra api’den çekeceğimiz verileri <strong>db </strong>adında bir json dosyasına yazıyoruz.</p><p><strong>db.json</strong></p><pre>{<br>   &quot;todos&quot;: [<br>              {&quot;todo&quot;: &quot;todos1&quot;},<br>              {&quot;todo&quot;: &quot;todos2&quot;},<br>              {&quot;todo&quot;: &quot;todos3&quot;}<br>            ]<br>}</pre><p>db.json dosyamız nerede ise oranın path’ini yazarak aşağıdaki komutu çalıştırıyoruz.</p><pre>json-server --watch db.json</pre><p>dosyamız, proje klasörümüzün içinde ise bir yol belirtmemize gerek yok. Başka bir yerde ise<strong> </strong>dosyanın tam yolunu vererek komutu çalıştırmalıyız. Bu işlemlerden sonra todolarımız <strong>“localhost:3000/todos” </strong>endpoint’inde görüntülenecektir. Başka endpoint’lerde ekleyebiliriz. Bu kütüphanenin bir çok özelliği vardır incelemek için <a href="https://github.com/typicode/json-server">github </a>adresine gidebilirsiniz.</p><blockquote>Redux-thunk’ın kurulumunu yaptığınızı varsayıyorum.Yapmadıysanız yukarıda nasıl yapıldığını anlattım.</blockquote><p>Bu state için öncelikle ,yeni bir <strong>action </strong>ve reducer’a yeni bir <strong>case </strong>eklemeliyiz.</p><blockquote><em>src/redux/actions/</em><strong><em>actionTypes.js</em></strong></blockquote><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a39cb73596fd216cfdb04534fb04b574/href">https://medium.com/media/a39cb73596fd216cfdb04534fb04b574/href</a></iframe><blockquote><em>src/redux/reducers/</em><strong><em>todoReducer.js</em></strong></blockquote><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d1e83ea2ed34da51115aeee8d61abbb3/href">https://medium.com/media/d1e83ea2ed34da51115aeee8d61abbb3/href</a></iframe><blockquote><em>src/redux/actions/</em><strong>thunkActions<em>.js</em></strong></blockquote><p>Api isteği yapan fonksiyonlarımızı tutacak bir dosya oluşturuyoruz. Direkt <strong>actionCreators.js </strong>dosyamıza da ekleyebiliriz ama daha modüler ve uygulama daha kompleks hale gelmesin diye ayrı bir dosyada yapmak daha mantıklıdır. Ben burada daha genel bir dosya oluşturdum ama çoğu gerçek hayat projelerinde, yaptığı işlemlere göre bu fonksiyonlar gruplandırılıp ayrı dosyalarda tutulur.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f9d9fa9404a1926d6df270f7007fdfc5/href">https://medium.com/media/f9d9fa9404a1926d6df270f7007fdfc5/href</a></iframe><p>İsteğimizi yaptıktan <strong>sonra</strong> ,gelen datayı kullanarak bir action oluşturup dispatch metoduna parametre geçiyoruz. Bu işlemden de sonra, thunk middleware tekrar devreye girerek ,gelen action’ı kontrol edip obje ise reducer’a parametre geçiyor.</p><p>Bu fonksiyonu TodoSearch Component’inde kullanacağız. Component, ilk render işleminde bu verileri default olarak gösterecek. İlk render işleminde çalışacak fonksiyonu(<strong>getTodosApiRequest</strong>) ,react&#39;ın lifecycle metotlarından olan <strong>componentDidMount </strong>fonksiyonunu kullanarak kullanacağız.</p><blockquote><em>src/components/</em><strong><em>TodoSearch.js</em></strong></blockquote><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3d98b89f2b8c697c13665ddd42fe7e63/href">https://medium.com/media/3d98b89f2b8c697c13665ddd42fe7e63/href</a></iframe><p><strong>mapDispatchToProps </strong>fonksiyonunda component ilk render olduğunda tetiklenecek action’ı tanımladık. Action’ın olduğu dosyayı component’imizde import etmeyi unutmamalıyız(<strong>5.satırda</strong>). Action’ı ,Component ilk render işleminden hemen önce çalıştırmak için <strong>componentDidMount </strong>fonksiyonundan faydalanıyoruz(<strong>12.satırda</strong>). Bu sayede uygulamamız ilk açıldığında, api’den gelen 3 tane todo’yu -db.json dosyasında bu kadar verdik.- default olarak gösterecek.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*scftuhuHJU3T1t16gEzK7A.png" /><figcaption>Uygulamamız ilk açıldığındaki ekran görüntüsü.</figcaption></figure><h3>redux-logger middleware</h3><p>State her değiştiğinde; hangi action’ın çalıştığı, önceki state’in ve değiştikten sonraki state’in son halini konsola yazar. Bu sayede kullanıcının ne yaptığı an an takip edilebilir.</p><h4>Nasıl Kullanılır ?</h4><p>Projemize dahil etmek için aşağıdaki komutu çalıştırıyoruz.</p><pre>npm i --save redux-logger</pre><p>Yine aynı şekilde <strong>applyMiddleware </strong>fonksiyonu ile <strong>logger </strong>midleware’mizi <strong>createStore </strong>fonksiyonuna parametre geçiyoruz.</p><pre>import { applyMiddleware, createStore } from &#39;redux&#39;;<br>import logger from &#39;redux-logger&#39;<br>import rootReducer from &#39;./reducers/index&#39;;</pre><pre>const store = createStore(rootReducer,applyMiddleware(logger))</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/524/1*Vfs1rGeV8_f6xn417W7hkQ.png" /><figcaption>Uygulamamız ilk açıldığında ,gerçekleşen api isteği sonucu <strong>react-logger </strong>middleware’nin çalışması.</figcaption></figure><p>Örneğe <a href="https://github.com/AliYildizoz909/redux-example/tree/redux-thunk-example">buradan </a>ulaşabilirsiniz.</p><p>Redux ile ilgili yazılarımın sonuna geldik. Umarım faydalı olmuştur. Görüşmek üzere.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d46445f7b9c6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/software-development-turkey/reactta-redux-ile-middleware-kullan%C4%B1m%C4%B1-ve-redux-thunk-d46445f7b9c6">React’ta redux ile middleware kullanımı ve redux-thunk.</a> was originally published in <a href="https://medium.com/software-development-turkey">SDTR</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[React’ta redux nasıl ve neden kullanılır ?]]></title>
            <link>https://medium.com/software-development-turkey/reactta-redux-nas%C4%B1l-ve-neden-kullan%C4%B1l%C4%B1r-f8e14ff1fdbc?source=rss-a62137e94030------2</link>
            <guid isPermaLink="false">https://medium.com/p/f8e14ff1fdbc</guid>
            <category><![CDATA[store]]></category>
            <category><![CDATA[redux]]></category>
            <category><![CDATA[react-redux]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[using-redux-to-react]]></category>
            <dc:creator><![CDATA[Ali Yıldızöz]]></dc:creator>
            <pubDate>Wed, 24 Jun 2020 07:37:56 GMT</pubDate>
            <atom:updated>2024-04-24T21:35:48.974Z</atom:updated>
            <content:encoded><![CDATA[<h3>React’ta redux nasıl kullanılır ?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*meCFnZ5MK_7Fu1ogYfBvNQ.png" /></figure><p>Merhaba Arkadaşlar,</p><p>Önceki yazımda redux’ın ne olduğunu,çalışma mantığını anlatmaya çalıştım. O yazıma <a href="https://medium.com/software-development-turkey/redux-nedir-ve-neden-kullan%C4%B1l%C4%B1r-68153ad3adfb">buradan </a>ulaşabilirsiniz.</p><p>Bu yazımda ise react’a redux’ı nasıl kullanacağız, neden ihtiyacımız var vb. soruları cevaplamaya çalışacağım.</p><h3>React’a redux’a neden ihtiyacımız var ?</h3><p>React’ta component’ler sürekli bir biri ile etkileşim içindedir. Bunun nedeni bir birinin state’ini kullanmalarıdır. Örneğin bir sayfanın navbar’ında login olan kişinin ismini gösteriyoruz. Bu isim, navbar component’inin state’dir. Daha sonra kişinin profil sayfasına gittiğimizde orada da ismini görürüz. Bu ismi öğrenmek için, her seferinde servisimize istek atmak çok kullanışlı olmayacağından, bu isim bilgisini tutan component’ten bilgiyi almak ,daha hızlı ve efektif olacaktır. Bu olaya <strong>component drilling</strong> adı verilir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/392/1*EYZagHfor3PAcJ6I3iB8jQ.png" /><figcaption>State değiştikten sonra component’lerin kendini güncellemesi.</figcaption></figure><p>Bu durum bir kaç sorunu beraberinde getiriyor. Bunlardan ilki bir component’in durum bilgisine ihtiyaç varsa bu bilgi her zaman üst katmandan gelmek zorundadır. Bu durumda bu bilgi için her zaman üst katmanlara bağımlı olacağız. İkincisi ise , component sayıları arttıkça paralel olan component’ler bir birinin state’ine ihtiyaç duyduğu zaman ; state’i, bir birine direkt göndermeyecekleri için bu durum ikisininde atası olan bir component’te state‘i tutmaya itecektir. Her zaman state’i merkezileştirme gibi bir yapıya gidilecektir.</p><p>Redux’ın buna sağladığı çok basit bir çözümü vardır. State’leri store adında bir yapıda tutar. Tüm component’ler bu store’ bağlıdır. Bir component ,state ile ilgili bir işlem yaparsa ,store’un ilgili state’ine abone olan componen’tler otomatik kendini güncelliyor. Bu sayede tüm component’ler tek bir yapıyla muhatap olduğu için daha yönetilebilir bir state oluyor.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*wwon5sqgPbUnyScQFfTV7Q.png" /><figcaption>Bir component’in store güncellemesi sonucu etkilenen componentler.</figcaption></figure><h3>Nasıl kullanılır ?</h3><p>Öncelikle iki tane kütüphaneye ihtiyacımız var <strong>redux </strong>ve <strong>react-redux</strong>.</p><p>Bunları indirmek için aşağıdaki komutları kullanıyoruz.</p><pre>npm install redux<br>npm install react-redux<br><strong>//tabii siz hangi paket yönetim aracını kullanıyorsanız ona göre indirin.</strong></pre><h3><strong><em>Reducer oluşturma.</em></strong></h3><p>Store’umuz tek bir state’i tutmayacağı için reducer’larımız da birden fazla olacaktır. Her bir reducer store’un ilgili state’i ile işlem yapacaktır.</p><p>Bu state’lere hem başlangıç değerlerini vermek hemde reducer’ların ilgileneceği state’i belirtmek için <strong>initialState.js </strong>adında bir dosya oluşturuyoruz.</p><p><strong>örnek initialState dosyası</strong></p><pre> export default {<br>        categories:[],<br>        currentCategory: {}<br>}</pre><p><strong>örnek reducer dosyası</strong></p><pre>import * as actionTypes from &quot;../actions/actionTypes&quot;<br>import <strong>initialState </strong>from &quot;./initialState&quot;;</pre><pre>export default function changeCategoryReducer(state = <strong>initialState</strong>.currentCategory, action) {</pre><pre>     switch (action.type) {<br>            case actionTypes.CHANGE_CATEGORY:<br>                return action.payload<br>            default:<br>                return state;<br>    }</pre><pre>}</pre><p>Dikkat ederseniz reducer, initialState’tin sadece ilgili state’ini değiştiriyor.</p><h3>Store oluşturma.</h3><p>createStore fonksiyonunu kullanarak store’umuzu oluşturuyoruz.</p><pre>import { <strong>createStore </strong>} from &quot;redux&quot;;<br>import reducer from &quot;./reducer&quot;;</pre><pre>const store = <strong>createStore</strong>(reducer);</pre><p>Reducer’larımız birden fazla ise redux’ın <strong>combineReducers </strong>fonksiyonunu kullanabiliriz.</p><pre>import { <strong>createStore</strong>,<strong>combineReducers </strong>} from &quot;redux&quot;;<br>import reducer1 from &quot;./reducer1&quot;;<br>import reducer2 from &quot;./reducer2&quot;;</pre><pre>const reducers = <strong>combineReducers</strong>({<br>         reducer1,<br>         reducer2<br>});<br>const store = <strong>createStore</strong>(reducers);</pre><p>createStore fonksiyonu geriye bir obje dönüyor.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/719/1*f982O_JzVXfjXrJC1H0zIw.png" /></figure><ul><li><strong>dispatch: </strong>reducer’ı tetikleyen fonksiyon. Bu fonksiyonu doğrudan kullanmayız. Bu işlemi react-redux kütüphanesi ile gerçekleştiririz.</li><li><strong>getState: </strong>mevcut state’ti dönen fonksyion. Spesifik olarak bir state dönmez. Reducer’da kullanılan state’leri döner.</li><li><strong>replaceReducer: </strong>adından da anlaşılacağı gibi mevcut reducer’ları, parametre olarak geçilen reducer’larla değiştirir. Örneğin aynı state üzerinde farklı işlemler yapan iki reducer’imiz varsa, bazı durumlarda bu iki reducer’i değiştirmek isteyebiliriz.</li><li><strong>subscribe: </strong>component’leri bu fonksyion ile abone ederiz. Bu fonksiyonu da doğrudan kullanmayız. React-redux kütüphanesinin metotları ile bunu gerçekleştiririz.</li></ul><h4>Provider</h4><p>Store’umuzu component’ler de kullanmak için bu nesne ile uygulamamızı sarmalıyoruz. Parametre olarak store’u veriyoruz.</p><pre>import { Provider } from &#39;react-redux&#39;;<br>import { createStore,combineReducers } from &quot;redux&quot;;<br>import reducer1 from &quot;./reducer1&quot;;<br>import reducer2 from &quot;./reducer2&quot;;</pre><pre>const reducers = combineReducers({<br>         reducer1,<br>         reducer2<br>});</pre><pre>const store = createStore(reducers);</pre><pre>ReactDOM.render(&lt;Provider store={store}&gt;&lt;App /&gt;&lt;/Provider&gt;, document.getElementById(&#39;root&#39;));</pre><h4>Connect</h4><p>Component’leri state bağlayarak “this.props” üzerinden state ve action’lara erişim sağlamak için kullanılır.</p><pre>import { connect } from &quot;react-redux&quot;;<br>import { bindActionCreators } from &#39;redux&#39;<br>import { action1 } from &quot;../redux/actions&quot;</pre><pre>class Component1 extends <strong>Component</strong> {</pre><pre>    render() {<br>           return (</pre><pre>             &lt;div&gt;<br>                 &lt;h1&gt;{this.props.state1}&lt;/h1&gt;<br>                 &lt;button onClick={this.props.action1}&gt;click&lt;/button&gt;</pre><pre>             &lt;/div&gt;</pre><pre>           )<br>        }<br>}<br>function mapStateToProps(state) {<br>      return { state1: state.reducer1<br>               state2: state.reducer2 <br>            }<br>}</pre><pre>function mapDispatchToProps(dispatch) {<br>      return { <br>              action1: bindActionCreators(action1, dispatch)<br>              <strong>//action1: ()=&gt; dispatch(action1())</strong><br>             }<br>}</pre><pre>export default connect(mapStateToProps,mapDispatchToProps)(Component1);</pre><p><strong>mapStateToProps: </strong>bu fonksiyon ile state’imize , direkt “this.props” üzerinden ulaşabiliyoruz. this.props.state1 bu fonksiyona parametre olarak gelen state üzerinden , erişmek istediğimiz state’i hangi reducer değiştiriyorsa onu kullanmalıyız. Örneğin reducer1 state1&#39;i değiştirip geriye yeni bir state1 döndüğü için parametre olarak gelen state üzerinden reducer1&#39;i çağırmalıyız state.reducer1.</p><p><strong>mapDispatchToProps: </strong>dispatch’i props üzerinden tetiklemek için kullanılır.</p><p><strong>bindActionCreators: </strong>bu fonksiyon , verilen parametrelere göre bir tetiklemeye hazır bir action yaratıyor. Yaptığı işlem buna eşdeğerdir. dispatch(action1())</p><p>Component’i bağlamak için en sonda component’i ,parametre olarak vermeliyi unutmamalıyız.</p><h3>Örnek</h3><p>Örnek olarak bir todo uygulaması yapacağız. Basit bir şekilde ekleme , silme ve arama işlemleri olacak.</p><p>Öncelikle aşağıdaki komut ile “redux-example” adında bir react projesi oluşturuyoruz. Sonrasında <strong>redux </strong>ve <strong>react-redux</strong> kütüphanelerini yüklüyoruz.</p><pre>npx create-react-app redux-example<br>npm install redux<br>npm install react-redux</pre><p>Uygulamamızın görünümü için <strong>reactstarp</strong> ve <strong>bootstrap</strong> kullanacağız. Bu kütüphanelerin yükleme komutları da aşağıdadır.</p><pre>npm install --save bootstrap<br>npm install --save reactstrap react react-dom</pre><p>Css’lerin etki etmesi için <strong>index.js</strong> dosyamıza bootstrap css dosyasını import etmemiz gerekir.</p><pre>import &#39;bootstrap/dist/css/bootstrap.min.css&#39;;</pre><p>Kütüphanelerimizi yükledikten sonra, <strong>src </strong>dosyamıza redux adında bir klasör oluşturuyoruz. Bu klasöre action’ larımız ile ilgili dosyalar koymak için <strong>actions </strong>, reducer’larımız için <strong>reducers </strong>adında iki klasör daha açıyoruz.</p><blockquote>src/redux/actions/<strong>actionTypes.js</strong></blockquote><p>Bu dosyada todo’larımız üzerinde yapacağımız işlemlerin tiplerini belirliyoruz.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2db2a45a517bfd367aa62da8037fbb7f/href">https://medium.com/media/2db2a45a517bfd367aa62da8037fbb7f/href</a></iframe><blockquote>src/redux/actions/<strong>actionCreators.js</strong></blockquote><p>Action’ları tek tek üretmek zor ve mantıksız olacağından <strong>actionCreator</strong>’lardan faydalanırız. Verdiğimiz parametreye göre action’larımız hızlı bir şekilde üretecek.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bee97ef49d950833e851b88fd62157c2/href">https://medium.com/media/bee97ef49d950833e851b88fd62157c2/href</a></iframe><blockquote>src/redux/<strong>initialState.js</strong></blockquote><p>Reducer’a state’in,başlangıç değerlerini ve state tiplerini belirtmek için bu dosyaya gerekli bilgileri veriyoruz.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8b0f9b871ff21175f828ec794482a326/href">https://medium.com/media/8b0f9b871ff21175f828ec794482a326/href</a></iframe><p><strong>todos: </strong>Tüm todolarımızı tutatacak array .</p><p><strong>filterTodos: </strong>todos üzerinde yaptığı arama sonuçlarını tutacak array. Bunu ayrı olarak yapmaktaki amaç; bir filtre işleminde todo’ları silmemek içindir.</p><blockquote>src/redux/reducers/<strong>todoReducer.js</strong></blockquote><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1edb88fc2b8a294d2b2e6826bf7cfe57/href">https://medium.com/media/1edb88fc2b8a294d2b2e6826bf7cfe57/href</a></iframe><p>Gelen action tipine göre ekleme silme ve arama işlemlerini gerçekleştiriyoruz.</p><p>Dikkat ederseniz her zaman yeni bir obje dönüyorum. Mutlaka objenin referansını değiştirmek zorundayız.</p><ul><li><strong>TODO_ADD: </strong>eski state’ti bir array’e kopyalayıp(<strong>Object spread opretaor</strong>) gelen todo’yu kopyalanan array’e ekliyorum.</li><li><strong>TODO_REMOVE: </strong>gelen todo hariç tüm todo’ları yeni bir array olarak dönüyorum.</li><li><strong>TODO_SEARCH: </strong>yine “action.payload” tan gelen değere göre arama yapıp filtre sonucunu “state.filterTodos” a set ediyorum.</li></ul><p><strong>Tüm case’lerde initialStat’e uygun bir obje dönmek zorundayız.Eğer reducer’ımız başka bir state üzerinde çalışsaydı, ona uygun dönülecekti.</strong></p><blockquote>src/redux/<strong>index.js</strong></blockquote><p>Bu dosyada reducer’larımızı combine ediyoruz. Bunun sonucunu <strong>createStore </strong>fonksiyonuna parametre geçeceğiz. Şimdilik bir tane reducer’ımız olduğu için onu gönderiyoruz.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/157437cc60fbe38898887787d76be1bc/href">https://medium.com/media/157437cc60fbe38898887787d76be1bc/href</a></iframe><blockquote>src/redux/<strong>configureStore.js</strong></blockquote><p>İndex dosyasından gelen reducer’ları burada ,configureStore fonksiyonunda , createStore fonksiyonuna parametre veriyoruz. Bu kısımda middleware’lerimizi de ekleyebiliriz.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b81c0a39f270b0370154c605654bdaef/href">https://medium.com/media/b81c0a39f270b0370154c605654bdaef/href</a></iframe><blockquote>src/<strong>index.js</strong></blockquote><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7702640ffd6fb659ebe40eb08b5fa823/href">https://medium.com/media/7702640ffd6fb659ebe40eb08b5fa823/href</a></iframe><p>Store’umuzu oluşturduktan sonra uygulamamızı Provider nesnesi ile sarmallayıp store’u parametre olarak gönderiyoruz.</p><blockquote>src/components/<strong>TodoAdd.js</strong></blockquote><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/103b199789a55061758d6f4ae8e25cba/href">https://medium.com/media/103b199789a55061758d6f4ae8e25cba/href</a></iframe><p><strong>mapDispatchToProps </strong>metodunda ekleme action’ını tanımladık. State ile bir işimiz olmadığı için <strong>connect</strong> fonksiyonunun ilk parametresini <strong>null </strong>geçtik.</p><p>Component’imiz çok basit bir işlem yapıyor. İnput’ta yapılan her değişkliği component’in state’inde tanımlanan todo değişkenine set ediyor. Butona tıklandığı zaman da bu değeri ,add action’ını kullanarak parametre geçiyor. Bu işlem sonucunda arka tarafta ,dispatch metodumuz action’ı reducer’ımıza parametre geçerek ilgili işlemi yapıyor.</p><blockquote>src/components/<strong>TodoSearch.js</strong></blockquote><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/44f44c881cfdc97683562cfabd0cee57/href">https://medium.com/media/44f44c881cfdc97683562cfabd0cee57/href</a></iframe><p>Bu component’in state’inde aranan değeri tutuyoruz. Bu aranan değer input’ta ,her değiştiğinde bir action tetikleniyor. Bu işlemden sonra reducer’ımızda filtreleme işlemi gerçekleşerek olası sonuçlar filterTodos state’ine set ediliyor. Eğer aranan değer boş ise tüm todo’ları değilse -demek ki aranan bir değer var- filtre sonucunu TodoList component’ine gönderiyoruz.</p><blockquote>src/components/<strong>TodoList.js</strong></blockquote><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ca5c0291f0288028ce36d9cc51e87e8b/href">https://medium.com/media/ca5c0291f0288028ce36d9cc51e87e8b/href</a></iframe><p>Bu component’te ise parent component’ten gelen todo’ları map fonksiyonu ile gösteriyoruz. Bir todo’nun silinme işlemini yine ekleme işlemi gibi bir butona bağlıyoruz. Bu butona tıklandığında ilgili todo ile beraber action fırlatılıyor. Reducer’a giden action TODO_REMOVE case’ine düştükten sonra filtreleme işlemi yapılarak yeni state geri dönülüyor. State değiştiği için todos state’ine abone olan tüm component’ler kendini render ediyor.</p><p>Bu uygulamada ,hem <strong>redux</strong> hemde <strong>component diriling </strong>yöntemlerini kullanarak state’lerimizi yönettik. TodoSearch component’inden TodoList component’ine ,todos state’ini parametre geçerek <strong>component diriling </strong>yöntemini, todos state’ini <strong>redux </strong>mağazasından TodoSearch component’imize göndererek <strong>redux store</strong> yöntemini kullandık.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodesandbox.io%2Fembed%2Fcocky-wood-nkx2e%3Ffontsize%3D14%26hidenavigation%3D1%26theme%3Ddark&amp;display_name=CodeSandbox&amp;url=https%3A%2F%2Fcodesandbox.io%2Fs%2Fcocky-wood-nkx2e%3Ffontsize%3D14%26hidenavigation%3D1%26theme%3Ddark&amp;image=https%3A%2F%2Fcodesandbox.io%2Fapi%2Fv1%2Fsandboxes%2Fnkx2e%2Fscreenshot.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codesandbox" width="1000" height="500" frameborder="0" scrolling="no"><a href="https://medium.com/media/95256d408e43d1cb0f926538137c44fd/href">https://medium.com/media/95256d408e43d1cb0f926538137c44fd/href</a></iframe><p>Örneğe <a href="https://github.com/AliYildizoz909/redux-example">buradan </a>ulaşabilirsiniz.</p><p>Sonraki yazımda redux middleware’lerini , redux-thunk ile asenkron işlemleri anlatacağım. Görüşmek üzere.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f8e14ff1fdbc" width="1" height="1" alt=""><hr><p><a href="https://medium.com/software-development-turkey/reactta-redux-nas%C4%B1l-ve-neden-kullan%C4%B1l%C4%B1r-f8e14ff1fdbc">React’ta redux nasıl ve neden kullanılır ?</a> was originally published in <a href="https://medium.com/software-development-turkey">SDTR</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>