Categorias
Tropeçando

Tropeçando 2 – Republish

dBpoweramp: CD Ripper & Audio Converter. Secure ripping to mp3, FLAC, m4a, Apple Lossless & WMA

CD extractor and multi converter from different audio and video codecs. Lots of advanced options for different formats.

A última de bluetooth - rede entre dois GNU/Linux

No trabalho você tem um pc ligado a internet via wifi, ethernet, ou similares. Você leva seu notebook que gostaria que estivesse conectado também, como fica? Você está no aeroporto com mais uma pessoa, os dois de notebook, só um modem 3G, como fica? E se vocês tiverem só um login da Vex, prestadora de acesso wifi, como fica? Você faz uma rede bluetooth entre os dois e compartilha a conexão, ora. (com exceção do primeiro, todos os comandos abaixo são como root)

Paje Online: Como Converter Vídeos no Linux?

Converter arquivos de vídeos e som no Linux, abrangendo os mais variados formatos e codecs, pode ser uma tarefa razoavelmente simples, bastando conhecer o programa certo. Nesta dica vamos apresentar o programa ffmpeg.

Comandos Básicos - Ubuntu Brasil

Muito embora o Linux possua diversas e ótimas interfaces gráfica (GUI\'s - Graphical User Interfaces) bastante amigáveis, dentre as quais destacamos o Gnome e KDE, como de resto todos os sistemas operacionais Unix, ainda requerem por vezes que façamos uso da linha de comando. O ambiente tradicional do Unix é o CLI (Command Line Interface), onde você digita os comandos para dizer ao computador o que ele deve fazer. Esse modo é extremamente poderoso e rápido, porém implica que você saiba para que serve cada comando e seus diversos parâmetros.

Instructables - Make, How To, and DIY

Aprenda a fazer tudo

PHP é à quinta-feira - Gerar uma password | Peopleware

Um conjunto de funções que os ajudarão a gerar uma password (ou qualquer outra string de caracteres aleatórios).

Color Hunter

crie e encontre paleta de cores a partir de imagens

Sua Língua » Arquivo » Não compre o novo VOLP! — 1ª parte

Blank/erase a DVD-RW | commandlinefu.com

Apagando um DVD-RW na linha de comando

Se eu soubesse que web 2.0 era isso… » CrisDias weblog

nth-child | Boas práticas de Desenvolvimento com Padrões Web

50 Creative and Inspiring 404 Pages | Webdesigner Depot

9 Interesting Facts To Know About a Website | Tools

Os top cinco erros não técnicos cometidos por desenvolvedores | Pacote201.com.br

Blog do Márcio d’Ávila » Cuidado - A fraude evoluiu

Dicas para evitar fraudes da internet.

Categorias
PHP

PHP 8.1: more on new in initializers

I could not agree more with Brent when he says concerning the "new in initializers"[1] feature:

PHP 8.1 adds a feature that might seem like a small detail, but one that I think will have a significant day-by-day impact on many people.

When I see this new feature, lots of places that use Dependency Injection[3] come to my focus as candidates to be impacted, such as application or infrastructure service classes. As a result, we will write a much cleaner and leaner code without giving up on good practices to write modular, maintainable and testable software.

The Dependency Inversion Principle[4] gives us decoupling powers. But we know that many classes will receive the same concrete implementation most of (if not all) the time.

So this is very common to see some variation of this code:

$someDependencyToBeInjected = FactoryClass::create();
$someService = new SomeServiceClass($someDependencyToBeInjected);

Important note: I will ignore for now Service Containers and frameworks features that deal with service instantiation, auto wiring, etc.

Think of a database query service class: you depend on a connection object. Every time you need to instantiate your database service class, you need to prepare the connection dependency and inject it at the service class. Database connections are a great example when you use the same concrete implementation more than 90% of the time.

The same applies to a Service class that you use to handle business logic and depends on QueryService and CommandHandler interfaces to do its job.

Before PHP 8.1 we have code like this:

// service class to apply business logic
// most standard
class DefaultLeadRecordService implements LeadRecordService
{
    public function __construct(
        private LeadQueryService $queryService,
        private LeadCommandHandler $commandHandler
    ) {
    }
}

// infrastructure class to match the interface -- a DBAL concrete class
// sneakily allowing a "default" value, but also open to Dependency Injection
// but not that great
class DbalLeadQueryService implements LeadQueryService
{
    public function __construct(private ?Connection $connection = null)
    {
        if (!$this->connection) {
            $this->connection = Core::getConnection();
        }
    }
}

// instantiation would be something like -- given you have $connection already instantiated
$connection =  \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $config);

$service = new \Blog\Application\DefaultLeadRecordService(
    new \Blog\Infrastructure\DbalLeadQueryService($connection),
    new \Blog\Infrastructure\DbalLeadCommandHandler($connection),
);

// if we allow construct get default value
$service = new \Blog\Application\DefaultLeadRecordService(
    new \Blog\Infrastructure\DbalLeadQueryService(),
    new \Blog\Infrastructure\DbalLeadCommandHandler(),
);

While on PHP 8.1, you will be able to write it like so:

class DefaultLeadRecordService implements LeadRecordService
{
    public function __construct(
        private LeadQueryService $queryService = new DbalLeadQueryService(),
        private LeadCommandHandler $commandHandler = new DbalLeadCommandHandler()
    ) {
    }
}

// we see there is still room for new features here
// still not that great
class DbalLeadQueryService implements LeadQueryService
{
    public function __construct(private ?Connection $connection = null)
    {
        // waiting when `new initializers` feature allows static function as default parameters
        if (!$this->connection) {
            $this->connection = Core::getConnection();
        }
    }
}

$service = new \Blog\Application\DefaultLeadRecordService();

One liner! That saves a lot of typing, and the code remains very well structured. This is the type of significant impact we will have on our day-to-day work. We will write a more straightforward, robust and meaningful code, and we will ship features faster with high-quality code.

If you want to see a full implementation of this, check the code at https://github.com/rafaelbernard/blog-php-81-new-initializers

Test, our faithful friend

Writing tests is a must-have for any repository where quality is a requirement. However, the "New in initializers" feature does not force us to give up on a complete suite of tests. We still have all powers of unit or integration tests.

For application code, we would write unit tests and all the expectations for concrete dependencies:

<?php

namespace Test\Unit\Blog\Application;

use Blog\Application\DefaultLeadRecordService;
use Blog\Domain\LeadCommandHandler;
use Blog\Domain\LeadQueryService;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;

class DefaultLeadRecordServiceTest extends TestCase
{
    private const EMAIL = '[email protected]';

    private LeadQueryService|MockObject $leadQueryServiceMock;
    private LeadCommandHandler|MockObject $leadCommandHandlerMock;

    private DefaultLeadRecordService $service;

    protected function setUp(): void
    {
        parent::setUp();

        $this->leadQueryServiceMock = $this->getMockBuilder(LeadQueryService::class)->getMock();
        $this->leadCommandHandlerMock = $this->getMockBuilder(LeadCommandHandler::class)->getMock();

        $this->service = new DefaultLeadRecordService($this->leadQueryServiceMock, $this->leadCommandHandlerMock);
    }

    public function testCanAdd()
    {
        $this->leadQueryServiceMock
            ->expects(self::once())
            ->method('getByEmail')
            ->with(self::EMAIL)
            ->willReturn(false);

        $this->leadCommandHandlerMock
            ->expects(self::once())
            ->method('add')
            ->with(self::EMAIL)
            ->willReturn(1);

        $result = $this->service->add(self::EMAIL);

        self::assertEquals(1, $result);
    }

    public function testAddExistentReturnsFalse()
    {
        $this->leadQueryServiceMock
            ->expects(self::once())
            ->method('getByEmail')
            ->with(self::EMAIL)
            ->willReturn(['email' => self::EMAIL]);

        $this->leadCommandHandlerMock
            ->expects(self::never())
            ->method('add');

        $result = $this->service->add(self::EMAIL);

        self::assertFalse($result);
    }

    public function testCanGetAll()
    {
        $unsorted = [
            ['email' => '[email protected]'],
            ['email' => '[email protected]'],
            ['email' => '[email protected]'],
        ];

        $this->leadQueryServiceMock
            ->expects(self::once())
            ->method('getAll')
            ->willReturn($unsorted);

        $fetched = $this->service->getAll();

        $expected = $unsorted;
        asort($expected);

        self::assertEquals($expected, $fetched);
    }
}

Integration tests can be written for infrastructure code. For instance, we can use an SQLite database file to assert the logic for database operations.

Be aware that I am creating an SQLite temp database file on-demand for each test execution with $this->databaseFilePath = '/tmp/test-' . time(); and, thanks to the Dbal library, we can be confident that operations could work for any database.

-> It is highly recommended that, as an alternative, create a container with a seeded database that is compatible with your production database system.

<?php

namespace Test\Integration\Blog\Infrastructure;

use Blog\Infrastructure\DbalLeadQueryService;
use Doctrine\DBAL\Connection;
use Faker\Factory;
use Faker\Generator;
use Test\TestCase;

class DbalLeadQueryServiceTest extends TestCase
{
    private string $databaseFilePath;

    private Generator $faker;
    private Connection $connection;

    private DbalLeadQueryService $service;

    public function testCanGetAll()
    {
        $this->addEmail($email1 = $this->faker->email());
        $this->addEmail($email2 = $this->faker->email());
        $this->addEmail($email3 = $this->faker->email());

        $expected = [
            ['email' => $email1],
            ['email' => $email2],
            ['email' => $email3],
        ];

        $fetched = $this->service->getAll();

        self::assertEquals($expected, $fetched);
    }

    protected function setUp(): void
    {
        parent::setUp();

        $this->faker = Factory::create();

        $this->createLeadTable();

        $this->service = new DbalLeadQueryService($this->connection());
    }

    protected function tearDown(): void
    {
        parent::tearDown();

        $this->dropDatabase();
    }

    private function connection(): Connection
    {
        if (!isset($this->connection)) {
            $this->databaseFilePath = '/tmp/test-' . time();

            $config = new \Doctrine\DBAL\Configuration();
            $connectionParams = [
                'url' => "sqlite:///{$this->databaseFilePath}",
            ];

            $this->connection = DriverManager::getConnection($connectionParams, $config);
        }

        return $this->connection;
    }

    private function dropDatabase()
    {
        @unlink($this->databaseFilePath);
    }

    private function createLeadTable(): void
    {
        $this->connection()->executeQuery('CREATE TABLE IF NOT EXISTS leads ( email VARCHAR )');
    }

    private function addEmail(string $email): int
    {
        return $this->connection()->insert('leads', ['email' => $email]);
    }
}

Conclusion

PHP is evolving very quickly, with new features that enable more quality software, help developers and is even more committed to the fact that most of the web run flavours of PHP code. New features improve readability, software architecture, test coverage and performance. Those are all proof of a mature and live language.

Upgrade to PHP 8.1 and use "new in initializers" as soon as possible. You will not regret it.

If there is something you want to discuss more, let me know in the comments.

Links:

  1. New in initializers RFC
  2. Road to PHP 8.1
  3. Dependency Injection
  4. Dependency inversion principle
  5. Interface segregation principle
  6. Solid relevance
  7. SOLID principles
Categorias
Tropeçando

Tropeçando 104

CASL

CASL (pronounced /ˈkæsəl/, like castle) is an isomorphic authorization JavaScript library which restricts what resources a given client is allowed to access. It's designed to be incrementally adoptable and can easily scale between a simple claim based and fully featured subject and attribute based authorization. It makes it easy to manage and share permissions across UI components, API services, and database queries.

The Danger of Dark Patterns (With Infographic)

Are manipulative design techniques undermining your product and leading users to make bad decisions? Here’s how to avoid dark patterns and create ethical products that enhance customer trust.

Dark patterns are a popular design topic but defining them can be difficult. That’s because they’ve become so prevalent that many have been adopted as design conventions. It’s crucial to understand these manipulative techniques in order to create ethical products that enhance customer trust.

DevOps: Shift Left to Reduce Failure

The term “shift left” refers to a practice in software development in which teams focus on quality, work on problem prevention instead of detection, and begin testing earlier than ever before. The goal is to increase quality, shorten long test cycles and reduce the possibility of unpleasant surprises at the end of the development cycle—or, worse, in production.

Does varchar(n) use less disk space than varchar() or text?

Tl;DR: No. This is a recurrent doubt due to a real difference in many other database systems. But not for PostgreSQL. Although documentation explains that internally, the core system has a wise way to split and store string data internally instead of simply reserving the total space, it is hard to believe. Here we have proof that there is no real difference.

How to ease the pains of testing legacy code?

Practically every programmer in their career struggled with working on a legacy project or one in which at least part of the job involved some kind of legacy code. I will show you some tips and tricks which will make writing unit tests for legacy applications much easier and less hurtful. Let’s go deep into testing legacy code!

Categorias
Miscelaneous

Tropeçando 103

What is Domain-Driven Design (DDD)

A definition of DDD as a software design discipline

How to refactor without overtime and missed deadlines

A lot of software engineers, including myself, are passionate about code quality. This striving for a well-shaped codebase, while getting things done could cost one quite a few hours and nerves, though. I'm constantly looking for ways to achieve these two goals without significant trade-offs. Stand by for the current state.

How to test a PHP app? PHP unit testing and more

Do you really need to create tests? Of course, there are many reasons to do so – improved quality of software, decreased risks while making changes in the code, identifying errors, checking business requirements, improving security…I could go on and on with that. The point is – tests do make a difference.

Application Modernization Isn’t Just Fighting Legacy Tech

When radical innovations were rare, businesses could afford to treat application modernization as a sporadic reaction to change. A decade ago, most organizations modernized only when they were compelled to.

However, in the era of open-source and continuous innovation, modernization can’t be an isolated, one-off project. Businesses need to embrace a culture that celebrates change to thrive in the digital age. According to a report by F5, the past year has witnessed 133% growth in application modernization.

Responsible tech playbook

As technology becomes more central to peoples' lives, and to what businesses do, and how they succeed, the ethics of technology must come into sharper focus.

Despite technology becoming a critical part of what enterprizes do, it's not always clear how to approach and apply technology in an ethical or responsible way.

The Responsible tech playbook is a collection of tools, methods, and frameworks that help you to assess, model and mitigate values and risks of the software you are creating with a special emphasis on the impact of your work on the individual and society.

Categorias
PHP Programação

PHP Memory Usage and Performance Improvements Tips

Memory usage and performance improvements make everybody happier, from end-user to cloud and infrastructure engineers. And they are all right, and this is an optimization that we should try to achieve as much as possible.

I am also keeping this page for a reference to my future self because we cannot rely too much on our memory, and that will be a good reference I want to re-visit. I will make constant updates on this page.

Use objects with declared properties over array

Arrays have a larger footprint to avoid constant memory pointers reassignments. It then reserves large amounts of memory when more elements or indexes are added.

Image for array vs object memory usage

Be careful to self-referencing that would prevent garbage collector from work

Garbage collector is working as expected when the internal reference count (how may times a value is used) reaches zero:

$x = "foobar";    // refcount = 1
$y = $x;            // refcount = 2
unset($x);      // refcount = 1
unset($y);      // refcount = 0 -> garbage collector will be happy ==> Destroy!

But self-referencing can be tricky:

$x = [];            // refcount = 1
$x[0] =& $x;    // refcount = 2
unset($x);      // refcount = 1
                    // It will never come to zero due to cycle

The cycle collector will eventually destroy it, but it will hang on memory for a while anyway.

Sprintf vs double/single quote concatenation

A very common use case is string concatenation or interpolation when you want to add a variable into a static string. It is interesting to note that:

If you have PHP < 7.4, use double-quote interpolation or single quote concatenation over sprintf function.

<?php 

$this->start($loop);

ob_start();

for ($i = 0; $i < $this->loop; ++$i) {
    print 'Lorem '.$i.' ipsum dolor sit amet, consectetur adipiscing elit. Proin malesuada, nisl sit amet congue blandit';
}

ob_end_clean();

return $this->end();

If you have PHP greater than 7.4, use sprintf:

<?php 

$this->start($loop);

for ($i = 0; $i < $this->loop; ++$i) {
    $value = sprintf('Lorem %s ipsum dolor sit amet, consectetur adipiscing elit. Proin malesuada, nisl sit amet congue blandit', $i);
}

return $this->end();

PHP Benchmarking

PHPBench.com was constructed as a way to open people's eyes to the fact that not every PHP code snippet will run at the same speed. You may be surprised at the results that this page generates, but that is ok. This page was also created so that you would be able to find discovery in these statistics and then maybe re-run these tests in your own server environment to play around with this idea yourself, by using the code examples (these code examples are automatically generated and as the code in my .php files change, so do they).

PHP benchmarks and optimizations

Collection of tests and benchmarks for common operations in PHP. Tests run on several versions of PHP. There is an option to compare different solutions for the same problem to compare performances between them, such as checking values with isset against !empty.

Categorias
Tropeçando

Tropeçando 100

How Exception to the Convention Does More Harm than Good

We have a project, and to make it easier, we use specific standards. E.g., we use spaces in every file.

But sometimes, we get to a situation when these standards are stressful. We don't understand them and just want them to bend over. How does our short-term need for pleasure affects long-term well-being of the project?

Splitting a Domain Across Multiple Bounded Contexts

How designing for business opportunities and the rate of change may give you better contexts.

Your Jest Tests are Leaking Memory

Jest is designed in a way that makes memory leaks likely if you’re not actively trying to squash them. For many test suites this isn’t a problem because even if tests leak memory, the tests don’t use enough memory to actually cause a crash. That is, until you add one more test and suddenly the suite comes apart. In this article, we’ll walk through why it’s so easy for Jest to leak memory, how to tell if your tests have a memory leak, and then how to debug and eliminate leaks in your tests. Even if you don’t use Jest, it’s still useful to understand the process in case you need to debug memory leaks in other javascript code.

How to handle flaky tests

Flaky tests are automated tests that are non-deterministic. That means they may pass or fail when executed against the same build artifact or deployed system.

If you’ve ever retried the execution of a failed test run in your CI/CD pipeline tool without any code or config changes in order to get a failing test case to pass, that’s an indicator that you have a flaky test case.

Why Serverless Teams Should Embrace Continuous Refactoring

Serverless adoption brings promises to many organizations! Promises of cost efficiency, engineering efficiency, and business efficiency. However, serverless is not a technology that, once successfully adopted, can be left unattended to rust.

The motivation to adopt serverless should never be to get the job done and never look back. If you adopt it with this attitude you will inevitably get into the legacy migration juggernaut that will haunt you much sooner than you anticipated.

Categorias
Tropeçando

Tropeçando 98

(Operating Lambda: Debugging code – Part 1)[https://aws.amazon.com/blogs/compute/operating-lambda-debugging-code-part-1/]
(Operating Lambda: Debugging configurations – Part 2)[https://aws.amazon.com/blogs/compute/operating-lambda-debugging-configurations-part-2/]
(Operating Lambda: Debugging configurations – Part 3)[https://aws.amazon.com/blogs/compute/operating-lambda-debugging-integrations-part-3/

In the Operating Lambda series, I cover important topics for developers, architects, and systems administrators who are managing AWS Lambda-based applications. This three-part series discusses core debugging concepts for Lambda-based applications.

(Those pesky pull request reviews)[https://jessitron.com/2021/03/27/those-pesky-pull-request-reviews/]

They’re everywhere. In Slack: “hey, can I get a review on this?” In email: “Your review is requested!” In JIRA: “8 user stories In-Progress” (but code-complete). In your repository: 5 open pull requests. They’re slowing your delivery. They’re interrupting your developers.

How can we get people to review pull requests faster??

(Operating Lambda: Using CloudWatch Logs Insights)[https://aws.amazon.com/blogs/compute/operating-lambda-using-cloudwatch-logs-insights/]

In the Operating Lambda series, I cover important topics for developers, architects, and systems administrators who are managing AWS Lambda-based applications. This three-part series discusses monitoring and observability for Lambda-based applications and covers:

  • Using Amazon CloudWatch, CloudWatch Logs Insights, and AWS X-Ray to apply monitoring
    across services.
  • How existing monitoring concepts apply to Lambda-based applications.
  • Troubleshooting application issues in an example walkthrough.
    This post explains how to use CloudWatch Logs Insights in your serverless applications.

(CDK Lambda Deployment takes about a minute - how about sub second Function Code Deployment?)[https://aws-blog.de/2021/04/cdk-lambda-deployment-takes-about-a-minute-how-about-sub-second-function-code-deployment.html]

Creation of Lambda infrastructure with the CDK is really powerful. Updating the Function code is really slow. Here is a fix for that to get to a sub-second Lambda function deployment time.

(Best practices for developing cloud applications with AWS CDK)[https://aws.amazon.com/blogs/devops/best-practices-for-developing-cloud-applications-with-aws-cdk/]

In this post, we discuss strategies for organizing the development of complex cloud applications with large teams, using the AWS Cloud Development Kit (AWS CDK) as a central technology. AWS CDK allows developers and administrators to define their cloud applications using a familiar programming language, such as TypeScript, Python, Java, or C#. Applications are organized into stages, stacks, and constructs, which allows for modular design techniques in both runtime logic (such as AWS Lambda code or containerized services) and infrastructure components such as Amazon Simple Storage Service (Amazon S3) buckets, Amazon Relational Database Service (Amazon RDS) databases, and network infrastructure.

Categorias
Tropeçando

Tropeçando 96

Refinement Code Review

With the right environment, I can look a bit of code written six months ago, see some problems with how it's written, and quickly fix them. This may be because this code was flawed when it was written, or that changes in the code base since led to the code no longer being quite right. Whichever the cause, the important thing is to fix problems as soon as they start getting in our way. As soon as I have an understanding about the code that wasn't immediately apparent from reading it, I have the responsibility to (as Ward Cunningham so wonderfully said) take that understanding out of my head and put it into the code. That way the next reader won't have to work so hard.

After all, many problems that code reviews seek to remedy are problems that only become problems when the code is read in the future.

MONITORING REPLICATION: PG_STAT_REPLICATION

PostgreSQL replication (synchronous and asynchronous replication) is one of the most widespread features in the database community. Nowadays, people are building high-availability clusters or use replication to create read-only replicas to spread out the workload. What is important to note here is that if you are using replication, you must make sure that your clusters are properly monitored.

The purpose of this post is to explain some of the fundamentals, to make sure that your PostgreSQL clusters stay healthy.

POSTGRESQL: WHAT IS A CHECKPOINT?

Check some information about checkpoints, mix and max wal size, how they operate toghether and what they play on our day-to-day database workload.

Checkpoints are a core concept in PostgreSQL. However, many people don’t know what they actually are, nor do they understand how to tune checkpoints to reach maximum efficiency. This post will explain both checkpoints and checkpoint tuning, and will hopefully shed some light on these vital database internals.

Boos your user defined functions in PostgreSQL

Using the RDBMS only to store data is restricting the full potential of the database systems, which were designed for server-side processing and provide other options besides being a data container. Some of these options are stored procedures and functions that allow the user to write server-side code, using the principle of bringing computation to data, avoiding large datasets round trips and taking advantage of server resources. PostgreSQL allows programming inside the database since the beginning, with User Defined Functions (UDFs). These functions can be written in several languages like SQL, PL/pgsql, PL/Python, PL/Perl, and others. But the most common are the first two mentioned: SQL and PL/pgsql. However, there may be “anti-patterns” in your code within functions and they can affect performance. This blog will show the reader some simple tips, examples and explanations about increasing performance in server-side processing with User Defined Functions in PostgreSQL. It is also important to clarify that the intention of this post isn’t to discuss whether Business Logic should be placed, but only how you can take advantage of the resources of the database server.

The career-changing art of reading the docs

Don’t wait for knowledge to find you through years of inefficient trial and error. Go get it. And the most convenient, comprehensive place to grab it was there in front of you all along.

Read the docs.

Categorias
Tropeçando

Tropeçando 93

CQRS Is an Anti-Pattern for DDD

Are you interested in new ways to build better software systems? If you work with distributed systems or build any kind of web application, you most likely have heard of the new trends like using Domain-Driven Design with Event-Sourcing and Command Query Responsibility Segregation (CQRS). Well, they are not exactly brand new. However, they are now becoming increasingly popular.

nip.io/

Dead simple wildcard DNS for any IP Address

Stop editing your /etc/hosts file with custom hostname and IP address mappings.

The Tighten Test: 12 Steps to a Better Team

Twenty years ago today, Joel Spolsky (who later co-founded Stack Overflow) published The Joel Test: 12 Steps to Better Code listing 12 metrics for rating the quality of a software development team. The premise is simple: you get 1 point for each “yes” answer, for a total score of up to 12 points.

Architecture decision records, also known as ADRs, are a great way to document how and why a decision was reached within a codebase. We’ve started to adopt them within the mobile team here at GitHub, documenting decisions that affect the iOS codebase and Android codebase, as well as decisions that affect both mobile clients.

ADRs are not the most common within open source codebases, but have gained more popularity since ~2017 within long-lived, “evolutionary” codebases like those in more enterprise-y settings.

So why write an ADR? Why spend time documenting something when a decision has already been made?

https://free-for.dev/

This is a list of software (SaaS, PaaS, IaaS, etc.) and other offerings that have free tiers for developers.