designpatterns Archives - ProdSens.live https://prodsens.live/tag/designpatterns/ News for Project Managers - PMI Sat, 22 Jun 2024 19:20:19 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 https://prodsens.live/wp-content/uploads/2022/09/prod.png designpatterns Archives - ProdSens.live https://prodsens.live/tag/designpatterns/ 32 32 Adding Middleware to .NET Desktop Applications https://prodsens.live/2024/06/22/adding-middleware-to-net-desktop-applications/?utm_source=rss&utm_medium=rss&utm_campaign=adding-middleware-to-net-desktop-applications https://prodsens.live/2024/06/22/adding-middleware-to-net-desktop-applications/#respond Sat, 22 Jun 2024 19:20:19 +0000 https://prodsens.live/2024/06/22/adding-middleware-to-net-desktop-applications/ adding-middleware-to.net-desktop-applications

Middleware is a common pattern in web development for handling cross-cutting concerns like logging, authentication, and error handling.…

The post Adding Middleware to .NET Desktop Applications appeared first on ProdSens.live.

]]>
adding-middleware-to.net-desktop-applications

Middleware is a common pattern in web development for handling cross-cutting concerns like logging, authentication, and error handling. However, in desktop applications, the concept isn’t as directly supported. This article explores how you can implement middleware-like behavior in .NET desktop applications using several design patterns and strategies.

Event Handling Middleware

One approach to implementing middleware in a .NET desktop application is through event handling. By intercepting and handling events centrally, you can insert middleware logic before the main application logic.

Example

public class Middleware
{
    public void Process(object sender, EventArgs e)
    {
        // Middleware logic
        Console.WriteLine("Middleware processing...");
    }
}

public class MainApplication
{
    public event EventHandler SomeEvent;

    public MainApplication()
    {
        Middleware middleware = new Middleware();
        SomeEvent += middleware.Process;
        SomeEvent += MainLogic;
    }

    public void MainLogic(object sender, EventArgs e)
    {
        // Main application logic
        Console.WriteLine("Main logic processing...");
    }

    public void TriggerEvent()
    {
        SomeEvent?.Invoke(this, EventArgs.Empty);
    }
}

In this example, the Middleware class intercepts the SomeEvent event before the MainLogic method processes it. This allows you to insert any necessary preprocessing logic.

Decorator Pattern Middleware

The decorator pattern is another effective way to add middleware-like behavior. This pattern involves wrapping functionality around specific methods, which can be particularly useful for key operations in your application.

Example

public interface IComponent
{
    void Execute();
}

public class MainComponent : IComponent
{
    public void Execute()
    {
        // Main application logic
        Console.WriteLine("Executing main component...");
    }
}

public class MiddlewareDecorator : IComponent
{
    private readonly IComponent _component;

    public MiddlewareDecorator(IComponent component)
    {
        _component = component;
    }

    public void Execute()
    {
        // Middleware logic before
        Console.WriteLine("Executing middleware before...");

        _component.Execute();

        // Middleware logic after
        Console.WriteLine("Executing middleware after...");
    }
}

// Usage
var mainComponent = new MainComponent();
var decoratedComponent = new MiddlewareDecorator(mainComponent);
decoratedComponent.Execute();

In this pattern, the MiddlewareDecorator wraps around the MainComponent, allowing you to insert logic before and after the main execution.

Pipeline Pattern Middleware

For a more structured approach, you can implement a pipeline pattern. This involves creating a series of middleware components that each process a request and pass it to the next component in the pipeline.

Example

public interface IMiddleware
{
    void Invoke(Context context, Action next);
}

public class Context
{
    // Context properties
}

public class Middleware1 : IMiddleware
{
    public void Invoke(Context context, Action next)
    {
        // Middleware logic
        Console.WriteLine("Middleware 1 processing...");
        next();
    }
}

public class Middleware2 : IMiddleware
{
    public void Invoke(Context context, Action next)
    {
        // Middleware logic
        Console.WriteLine("Middleware 2 processing...");
        next();
    }
}

public class Pipeline
{
    private readonly List<IMiddleware> _middlewares = new List<IMiddleware>();
    private int _currentMiddleware = -1;

    public void Use(IMiddleware middleware)
    {
        _middlewares.Add(middleware);
    }

    public void Execute(Context context)
    {
        _currentMiddleware = -1;
        Next(context);
    }

    private void Next(Context context)
    {
        _currentMiddleware++;
        if (_currentMiddleware < _middlewares.Count)
        {
            _middlewares[_currentMiddleware].Invoke(context, () => Next(context));
        }
    }
}

// Usage
var pipeline = new Pipeline();
pipeline.Use(new Middleware1());
pipeline.Use(new Middleware2());

var context = new Context();
pipeline.Execute(context);

In this pipeline pattern, each IMiddleware component processes the Context and then calls the next middleware in the sequence.

Conclusion

While .NET desktop applications don’t natively support middleware in the same way that web frameworks like ASP.NET Core do, you can still implement middleware-like behavior using event handling, the decorator pattern, or a custom pipeline. These strategies allow you to modularize and manage cross-cutting concerns such as logging, exception handling, and more in a clean and maintainable way.

By adopting these patterns, you can improve the structure and scalability of your desktop applications, making them easier to maintain and extend over time.

The post Adding Middleware to .NET Desktop Applications appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/06/22/adding-middleware-to-net-desktop-applications/feed/ 0
Functional Options Pattern in Go https://prodsens.live/2024/05/19/functional-options-pattern-in-go/?utm_source=rss&utm_medium=rss&utm_campaign=functional-options-pattern-in-go https://prodsens.live/2024/05/19/functional-options-pattern-in-go/#respond Sun, 19 May 2024 04:20:05 +0000 https://prodsens.live/2024/05/19/functional-options-pattern-in-go/ functional-options-pattern-in-go

Introduction In Go, the Functional Options pattern is a powerful technique that allows developers to provide flexible and…

The post Functional Options Pattern in Go appeared first on ProdSens.live.

]]>
functional-options-pattern-in-go

Introduction

In Go, the Functional Options pattern is a powerful technique that allows developers to provide flexible and customizable behavior to functions or methods by using functional options as arguments. This pattern is commonly used in Go libraries and frameworks to provide a clean and concise API for users.

What is the Functional Options Pattern?

The Functional Options pattern is a design pattern that leverages the power of higher-order functions and closures in Go to provide a flexible way of configuring objects or functions. Instead of using a large number of parameters or flags, the Functional Options pattern allows developers to pass in a series of functions, each of which configures a specific aspect of the object or function.

The Functional Options Pattern in Go addresses several challenges related to configuration and initialization of objects:

  1. Growing Number of Parameters: Traditional function signatures can become unwieldy as the number of parameters increases. The functional options pattern allows you to add new options without changing the function signature or breaking existing code.

  2. Readability: When a function takes multiple parameters, especially of the same type, it can be hard to remember the order and meaning of each. With functional options, each option is clearly labeled and self-explanatory, enhancing code readability.

  3. Default Values: The functional options pattern allows you to easily provide default values for your options. If an option is not provided when the function is called, the default value is used.

  4. Optional Parameters: In some cases, you might want to make some parameters optional. The functional options pattern allows you to do this easily, providing a flexible interface.

  5. Encapsulation and Validation: Each option is a function that can contain its own validation logic. This allows you to encapsulate the logic for each option and keep your main function clean and simple.

How does it work?

The Functional Options pattern works by defining a function type that represents an option. This function type takes a pointer to the object or function being configured as its argument and modifies it accordingly. The object or function being configured typically has a corresponding struct type that holds the configuration options as fields.

To use the Functional Options pattern, developers can define a variadic function that takes a series of option functions as arguments. Inside this function, the options are applied one by one to the object or function being configured.

What is variadic function?

A variadic function in Go is a function that can be called with any number of trailing arguments. This means you can pass as many arguments as you want into the variadic function.

The syntax for declaring a variadic function involves using an ellipsis … before the type of the last parameter. The function receives this as a slice of the type.

Here’s an example:

package main

import "fmt"

// This is a variadic function that accepts any number of integers
func sum(nums ...int) {
    fmt.Print(nums, " ")
    total := 0
    for _, num := range nums {
        total += num
    }
    fmt.Println(total)
}

func main() {
    sum(1, 2)
    sum(1, 2, 3)
    nums := []int{1, 2, 3, 4}
    sum(nums...)
}

In this example, sum is a variadic function that takes any number of int arguments. In the main function, we call sum with different numbers of arguments.

Example

Let’s illustrate the Functional Options pattern with an example. Suppose we have a Server struct that represents an HTTP server in Go. We want to provide users with the ability to configure various aspects of the server, such as the port it listens on, the timeout duration, and whether to enable logging.

First, we define the Server struct:

package main

import "fmt"

type Server struct {
    Host     string
    Port     int
    Protocol string
    Timeout  int
}

type ServerOption func(*Server)

func WithHost(host string) ServerOption {
    return func(s *Server) {
        s.Host = host
    }
}

func WithPort(port int) ServerOption {
    return func(s *Server) {
        s.Port = port
    }
}

func WithProtocol(protocol string) ServerOption {
    return func(s *Server) {
        s.Protocol = protocol
    }
}

func WithTimeout(timeout int) ServerOption {
    return func(s *Server) {
        s.Timeout = timeout
    }
}

func NewServer(options ...ServerOption) *Server {
    server := &Server{
        Host:     "localhost",
        Port:     8080,
        Protocol: "http",
        Timeout:  30,
    }

    for _, option := range options {
        option(server)
    }

    return server
}

func main() {
    server := NewServer(
        WithHost("example.com"),
        WithPort(9000),
        WithProtocol("https"),
        WithTimeout(60),
    )

    fmt.Printf("Server: %+vn", server)
}

The post Functional Options Pattern in Go appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/05/19/functional-options-pattern-in-go/feed/ 0
From Pixels to People: Redefining UX for Social Impact https://prodsens.live/2024/05/17/from-pixels-to-people-redefining-ux-for-social-impact/?utm_source=rss&utm_medium=rss&utm_campaign=from-pixels-to-people-redefining-ux-for-social-impact https://prodsens.live/2024/05/17/from-pixels-to-people-redefining-ux-for-social-impact/#respond Fri, 17 May 2024 10:20:26 +0000 https://prodsens.live/2024/05/17/from-pixels-to-people-redefining-ux-for-social-impact/ from-pixels-to-people:-redefining-ux-for-social-impact

We’re in the midst of a design revolution — a shift from simply creating user-friendly products to tackling…

The post From Pixels to People: Redefining UX for Social Impact appeared first on ProdSens.live.

]]>
from-pixels-to-people:-redefining-ux-for-social-impact

We’re in the midst of a design revolution — a shift from simply creating user-friendly products to tackling the intricate challenges of social issues. Whether it’s reimagining the delivery of portable drinking water in remote areas or questioning the healthcare system for the most vulnerable users, designers are now catalysts for meaningful change.

The key lies in challenging the ordinary, questioning things we take for granted. What may seem like minor inconveniences to us could be significant obstacles for others. This realization propels us toward a collective effort to forge a better world.

While the commercial aspect of design is ever-present, inspired by the spirit of the ‘first things first’ manifesto of 2000, we’re already armed with the tools to spearhead these transformative strategies. The call is for designers to recognize their influence and the consequences it carries.

Here are some vital considerations for this Herculean task:

Focus on Extreme Users:
How often should designers delve into users’ unique circumstances? By understanding the needs of extreme users, we address a broader range of pain points, catering to both situational and permanent problems.

Ethical and Cultural Sensitivity:
Recognizing and respecting diverse cultural contexts is pivotal. To avoid imposing unsuitable solutions, it’s crucial to align with local values. Acceptance and adoption by the target audience increase the chances of successful implementation.

Navigating Stakeholder Complexity:
Designers need a profound sense of empathy to navigate the complexities of multiple stakeholders. Mediation is key for aligning and forming agreements between involved parties. This complexity has been misunderstood, with the mere involvement of users not equating to the integration of design practices in social impact development.

Understanding Human Behavior Roots:
Sometimes, the root causes of problems stem from obvious yet challenging-to-pinpoint human behavior. This behavior results from complex influences, demanding designers to dig deeper for effective solutions.

Questioning Assumptions:
Unraveling assumptions is critical. By constantly questioning assumptions, designers uncover the root causes of social issues, paving the way for interventions with meaningful impact.

Systemic Thinking:
Considering solutions in a broader context, as advised by Eliel Saarinen, is paramount. Solutions are interwoven into a complex web of connections, and recognizing and accounting for these interdependencies are crucial for comprehensive and sustainable design.

As a global ux design studio we believe in this transformative journey, let’s make the design and language clear, engaging, and accessible to the masses of the world. Together, we can design a future where impact is not just a goal but a reality.

The post From Pixels to People: Redefining UX for Social Impact appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/05/17/from-pixels-to-people-redefining-ux-for-social-impact/feed/ 0
Reflexões sobre SOLID – Princípios Básicos https://prodsens.live/2024/05/02/reflexoes-sobre-solid-principios-basicos/?utm_source=rss&utm_medium=rss&utm_campaign=reflexoes-sobre-solid-principios-basicos https://prodsens.live/2024/05/02/reflexoes-sobre-solid-principios-basicos/#respond Thu, 02 May 2024 18:20:34 +0000 https://prodsens.live/2024/05/02/reflexoes-sobre-solid-principios-basicos/ reflexoes-sobre-solid-–-principios-basicos

Recentemente, tenho refletido bastante sobre alguns padrões de desenvolvimento, boas práticas e qualidade de código. Diversas ideias vêm…

The post Reflexões sobre SOLID – Princípios Básicos appeared first on ProdSens.live.

]]>
reflexoes-sobre-solid-–-principios-basicos

Recentemente, tenho refletido bastante sobre alguns padrões de desenvolvimento, boas práticas e qualidade de código. Diversas ideias vêm à mente com um tema tão abrangente, mas nesta série de artigos, gostaria de explorar especificamente o padrão de design que hoje conhecemos como “SOLID”, acrescentando algumas visões pessoais sobre sua filosofia, suas implementações concretas, e sobre cada letra que o compõe.

Neste artigo

Sobre suas origens
Sobre sua importância
Sobre sua definição
Sobre sua aplicabilidade
Reflexões finais
Textos complementares

Sobre suas origens

Sobre suas origens

Inicialmente propostos por Robert C. Martin em seu artigo “Design Principles and Design Patterns” de 2000, e posteriormente expandido por Michael Feathers para enclausurá-los no acrônimo que conhecemos hoje, os princípios SOLID foram cimentados no âmbito da Engenharia de Software como padrões essenciais para construção de software robusto, de fácil manutenção e escalável.

Esse é o resumo como conhecemos hoje, mais de 20 anos depois de sua concepção. É interessante vermos como, atualmente, essa visão atinge exatamente aquilo que toda pessoa engenheira de software, assim como qualquer corporação com ativos tecnológicos, busca como resultado: robustez, mantenabilidade, escalabilidade e qualidade.

Talvez por conta disso, SOLID acabou tornando-se uma “buzzword” nos últimos anos – ou seja, uma nomenclatura de fácil memorização, onde tanto aqueles que a posicionam como parte de seu conhecimento, quanto os que exigem essa competência para vagas de desenvolvimento, não necessariamente entendem ou pregam sua real aplicabilidade.

Isso não significa que sua base seja descartável, muito pelo contrário. Na verdade, o que acontece é que, vivendo na era de ouro do conteúdo, muitos focam em saber o que é, através de breves explosões de informação, mas nem todos se aprofundam no detalhe essencial de como funciona.

Sobre sua importância

Sobre sua importância

Quando falamos sobre SOLID (ou qualquer outro padrão arquitetural), é importante entendermos que são propostas para melhorarmos nosso trabalho na construção de software. Ou seja, não é algo que você instala em seu produto, uma dependência que você precisa acrescentar na aplicação, e muito menos algo que automaticamente criará um software competente e resolverá todos os problemas existentes e futuros. Os melhores ingredientes e panelas, quando entregues a alguém que não sabe cozinhar, não garante um alimento de qualidade.

Gostaria de destacar uma frase que, para mim, resume muito bem o porquê a adoção de tais propostas e padrões são uma constante em nossa área. Por mais que Martin esteja se direcionando a um tema mais específico em seu artigo no trecho abaixo (design patterns), creio que se aplica aos design principles de forma similar:

A definição essencial de um padrão de design é uma boa solução, bem estabelecida e conhecida, para um problema comum.

Robert C. Martin

Ou seja, tais paradigmas de desenvolvimento têm suas raízes em otimizar o processso de repetição. Ao longo de nossas carreiras, nos encontramos diversas vezes perante problemas em que o compartilhamento de código e ideias acaba apresentando-se naturalmente nas implementações. Duplicamos abordagens, consultamos construções anteriores para nos basearmos para as novas, e assim acabamos por evoluir o código de forma pouco consistente.

Graças aos exercícios de otimização realizados por grandes pensadores e pela comunidade dessa área ao longo dos anos, assim como ocorre com experimentações científicas, bases sólidas começam a surgir, servindo como pontos de partida para a solução de novos problemas. Blocos de códigos que antes eram duplicados agora são transformados em padrões que evitam tais duplicidades, propondo em seu lugar formas de distribuir lógicas de forma reaproveitável.

Sobre sua definição

Sobre sua definição

É nesse ponto que os princípios SOLID se encaixam: como ferramentas multifacetadas para resolver problemas e criar padrões que, conceitualmente, podem ser compartilhados entre membros da equipe, atuais ou futuros (considerando que todos busquem ratear esse conhecimento).

O acrônimo em si identifica os 5 princípios, não necessariamente em ordem de importância, mas na ordem para formar a palavra solid (sólido, em português), criando todo o vínculo criativo com o conceito de robustez:

  • Single Responsibility Principle: Princípio da Responsabilidade Única – Classes devem ter somente uma responsabilidade, e portanto, um único motivo para mudar;
  • Open/Closed Principle: Princípio do Aberto/Fechado – Classes devem ser fechadas para modificação, mas abertas para estensões;
  • Liskov Substitution Principle – Princípio de Substituição de Liskov – Classes devem ser facilmente substituíveis por suas subclasses;
  • Interface Segretation Principle – Princípio da Segregação de Interface – É melhor ter múltiplas interfaces específicas do que uma única interface genérica.;
  • Dependency Inversion Principle – Princípio da Inversão de Dependência – Classes devem depender de interfaces e abstrações, e não de implementações concretas.

Lendo somente o descritivo de cada letra, fica claro o porquê de tantas pessoas sentirem pouca confiança, ou muita confusão, nesse tópico. Digo isso por experiência: sem exemplos concretos ou demonstrações práticas, fica difícil compreender o que é “depender de abstrações e não de implementações”, ou até mesmo por qual motivo você precisaria “substituir uma subclasse” sem entender o devido contexto. O artigo original de Martin inclui constantemente exemplos, por mais abstratos que sejam, visto que é realmente um tema que pode passar despercebido se não conseguirmos visualizá-lo.

Um ponto interessante é que todos os conceitos abordam o tema de classes, exceto a letra I, que é o único que abrange a visão de interfaces. Ou seja, o nascimento do SOLID (e dos design patterns intrinsicamente ligados à sua filosofia), estão amarrados ao paradigma de OOP (Object-Oriented Programming, ou Programação Orientada a Objeto). É importante considerar o momento em que o artigo de Martin foi publicado, o que nos leva a pensar sobre a maturidade deste padrão e de sua longevidade naquele momento da história.

Isso não significa que os princípios SOLID sejam limitados somente a essa forma de implementação, pelo menos a nível de código. Outros modelos de desenvolvimento, como FP (Functional Programming – Programação Funcional), também podem beber da mesma fonte na questão de ideologia. O que quero dizer é que as ideias propostas, de termos criticidade na visão analítica sobre interfaces e entidades, podem fazer sentido também em outros contextos no qual lidaremos com essas iterações. Pretendo me aprofundar nessa ideia em partes futuras dessa série.

Sobre sua aplicabilidade

Ao entender as definições, podemos nos perguntar (eu, pelo menos, me perguntei na primeira vez): “Certo, isso tudo é muito interessante, mas e na prática? Como aplico esses conceitos no meu dia-a-dia? Como encontro oportunidades nos meus projetos para exercitar esses conhecimentos?”. Em meio a isso, existe um perigoso impulso que pode surgir, e é mais do que imprescindível reforçamos uma breve realidade: talvez você não precisará usar todas as letras, ao mesmo tempo, nem o tempo inteiro.

Os princípios SOLID, assim como qualquer padrão de design ou arquitetural, são um pouco mais empíricos do que outras áreas de conhecimento, exigindo que antes exercitemos nossa visão analítica do que somente o conhecimento técnico. Muitos problemas já conhecidos podem ser resolvidos de uma forma elegante, garantida e bem estabelecida – mas será que o problema que você possui se encaixa nessa solução? Ou você está buscando problemas para exercitar um ensaio de engenharia de software?

Com base nas minhas turbulentas primeiras experiências com esses paradigmas, consegui abstrair 3 pontos de partida que podem auxiliar no entendimento e facilitar a real aplicação da visão conceitual:

  1. Entender a essência abstraída: Antes de observar um código ou um exemplo prático, tente entender o que cada letra significa e o que ela propõe a nível de design, não de código. Ou seja, independentemente da linguagem ou framework em uso, quais os ganhos a nível da estrutura da aplicação? E as perdas? O próprio artigo de Martin é um excelente ponto de partida, mas como mencionei anteriormente, conteúdos online com linguagens mais informais (ou formais, conforme sua preferência) é o que menos falta em nossa era.

  2. Exercitar exemplos da sua realidade: De nada adiantará se os exemplos que você buscar fugirem muito dos seus conhecimentos concretos. Por mais reais que eles possam ser, é possível que eles não sejam condizentes com a sua realidade pessoal. Pessoalmente, tive bastante contato com FinTechs e projetos para chão de fábrica, e pensar em entidades relacionadas nesses contextos me ajuda bastante. E claro, pense em sistemas – o funcionamento de uma biblioteca municipal é algo bastante universal, por exemplo. Você pode até mesmo se basear em hobbies pessoais, visualizar algo que você gosta e domina e enxergá-lo com esse olhar analítico (quando penso em videogames, não consigo contar quantas vezes utilizei Super Mario 64, Banjo-Kazooie e Donkey Kong 64 como chaves de leitura).

  3. Buscar uma visão analítica: Comece a observar, no seu dia-a-dia, as entidades com as quais você trabalha, por mais simples que seu projeto seja. Será que ele já está otimizado o suficiente? Você consegue enxergar duplicidade de código ou de implementações? Existe algo que poderia ser mais genérico, ou talvez mais específico? Tome alguns minutos do seu dia para fazer essa reflexão e realmente busque entender o sistema por trás do produto – faça anotações, diagramas, um breve descritivo, e busque os relacionamentos. Não é fácil, e não é uma competência que surgirá da noite para o dia, mas o ideal é começar por algum lugar. Recomendo alternar também entre realizar essa atividade de forma singular e com uma pessoa parceira – analisar em equipe pode trazer insights que talvez passassem despercebidos, visto que cada pessoa terá seu próprio discernimento com base em sua bagagem de experiência.

Reflexões finais

Reflexões finais

Os princípios SOLID compõe um ferramental extremamente poderoso que, se utilizado da forma correta e ponderada, pode trazer diversos benefícios tanto ao software construído quanto àquele que o construiu. Não é a toa que, mesmo décadas depois da ideia original de Robert C. Martin ter sido publicada, continuamos falando sobre, pregando, ensinando e compartilhando ideias sobre o tema. A longevidade de todo o conceito, inclusive o fato de estar sofrendo iterações e evoluções por parte da comunidade, demonstra sua importância determinística.

Existem muitas pessoas que conseguem apresentar o SOLID de forma muito mais elegante, ou mais extravagante, do que eu. Minha ideia com esse artigo foi ruminar um pouco sobre o tema, e apresentar algumas ideias da minha filosofia sobre esse paradigma. 🙂

Obrigado por ter lido até aqui. Em futuras iterações desta série, quero me aprofundar um pouco mais em cada uma das letras, com alguns exemplos práticos e novamente refletindo sobre o tema.

Textos complementares

Design principles and design patterns, artigo de Robert C. Martin, 2000. Consultado em https://staff.cs.utu.fi/~jounsmed/doos_06/material/DesignPrinciplesAndPatterns.pdf

Design Patterns, livro referenciado por Martin como GOF (Gang of Four). Detalhamento sobre o livro pode ser encontrado na Wikipedia. Consultado em https://en.wikipedia.org/wiki/Design_Patterns

The post Reflexões sobre SOLID – Princípios Básicos appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/05/02/reflexoes-sobre-solid-principios-basicos/feed/ 0
A simple explanation of Adapter Pattern with Vue.js https://prodsens.live/2024/04/26/a-simple-explanation-of-adapter-pattern-with-vue-js/?utm_source=rss&utm_medium=rss&utm_campaign=a-simple-explanation-of-adapter-pattern-with-vue-js https://prodsens.live/2024/04/26/a-simple-explanation-of-adapter-pattern-with-vue-js/#respond Fri, 26 Apr 2024 21:20:28 +0000 https://prodsens.live/2024/04/26/a-simple-explanation-of-adapter-pattern-with-vue-js/ a-simple-explanation-of-adapter-pattern-with-vue.js

Hi there. Today I will share how I understood the Adapter Pattern using Vue.js. In short, the Adapter…

The post A simple explanation of Adapter Pattern with Vue.js appeared first on ProdSens.live.

]]>
a-simple-explanation-of-adapter-pattern-with-vue.js

Hi there.

Today I will share how I understood the Adapter Pattern using Vue.js. In short, the Adapter Pattern in Vue.js is used to “adapt” the interface of a component, method, or service so that it can be used in a compatible way with other parts of the code that expect a different interface.

This pattern is useful for integrating third-party components, APIs, or libraries that are not directly compatible with your Vue.js application, allowing for smoother, more flexible integration.

Here is an example:

1- I have a Home.vue file that will do a request to a random API (restcountries):


Here is the return of the API request:
Return of API

So, let’s imagine that we only need three variables from this response, formatted in a specific way:

interface Country {
  countryName: string
  countryCapital: string
  countryPopulation: number
}

2- I will create another file named adapters.ts and define a function to transform the current format into the one expected by the Country interface:

interface Country {
  countryName: string
  countryCapital: string
  countryPopulation: number
}

// Function that receives the API response and adapts it to an array of Country objects
export function getCountryAdapter(apiResponse: any): Country[] {
  // Check if is an array
  if (!Array.isArray(apiResponse)) {
    throw new Error('The API response is not a Array of countries.')
  }

  // Maps each country in the response to the Country object format
  const countries: Country[] = apiResponse.map((country: any) => ({
    countryName: country.name.common,
    countryCapital: country.capital[0],
    countryPopulation: country.population,
  }))

  return countries
}

3- Now, let’s call the adapter in the Home.vue file:


4- The final result is the response adapted to the interface 😊:

Adapted response

If you have any suggestions, please let me know. Thank you so much for reading!

The post A simple explanation of Adapter Pattern with Vue.js appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/04/26/a-simple-explanation-of-adapter-pattern-with-vue-js/feed/ 0
Factory Method https://prodsens.live/2024/03/22/factory-method/?utm_source=rss&utm_medium=rss&utm_campaign=factory-method https://prodsens.live/2024/03/22/factory-method/#respond Fri, 22 Mar 2024 05:20:51 +0000 https://prodsens.live/2024/03/22/factory-method/ factory-method

The factory design pattern is a fundamental creational pattern in object-oriented programming that deals with object creation. It…

The post Factory Method appeared first on ProdSens.live.

]]>
factory-method

The factory design pattern is a fundamental creational pattern in object-oriented programming that deals with object creation. It offers a way to create objects without directly specifying the exact class of the object you’re creating.

Here’s a breakdown of the key aspects of the factory design pattern:

  1. Centralized Object Creation: The factory pattern introduces a concept called a factory. This factory class acts as a central location for creating objects. Instead of directly using a class constructor, you interact with the factory to get the object you need.

2.Decoupling Creation from Usage: By using a factory, you decouple the code that uses the object (the client) from the specific class responsible for creating the object. This makes your code more flexible and adaptable.

3.Subclasses Define Object Types: The factory class typically defines an interface or an abstract class that represents the type of object being created. Subclasses of the factory then implement the logic to create specific concrete classes that adhere to the interface or inherit from the abstract class.

4.Factory Method Selection: The factory class might use different methods to decide which concrete class to instantiate. This decision could be based on factors like configuration settings, input parameters, or runtime conditions.

5.The factory design pattern offers several advantages:

Loose Coupling: The client code doesn’t depend on specific concrete classes, making it more adaptable to changes.
Flexibility: You can easily introduce new types of objects without modifying the client code.
Reusability: Factory classes promote code reuse by centralizing object creation logic.

Image description

Imagine a Car Dealership (Factory) that sells various car types (Concrete Classes). The dealership (Factory Class) doesn’t manufacture the cars itself, but it has partnerships with different car manufacturers (Subclasses).

1.Centralized Object Creation: You (Client) visit the dealership (Factory) to buy a car (Object). You don’t need to know the specifics of each manufacturer (Subclass) beforehand.

2.Decoupling Creation from Usage: The dealership (Factory) handles interacting with the manufacturers (Subclasses) to get you the car (Object) you need. You don’t have to contact manufacturers directly.

3.Subclasses Define Object Types: The dealership (Factory Class) might have different departments (Subclasses) specializing in sedans, SUVs, or trucks (Concrete Classes).

4.Factory Method Selection: The salesperson (Factory Method) at the dealership (Factory Class) might ask your preferences (Input Parameters) and then direct you to the relevant department (Subclass) to find your ideal car (Concrete Class).

5.The factory design pattern offers several advantages:

Loose Coupling: You are not tied to a specific manufacturer (Subclass). If you need a truck later, you can still go to the same dealership (Factory).
Flexibility: The dealership (Factory Class) can easily add new departments (Subclasses) to sell new car types (Concrete Classes) without affecting you.
Reusability: The dealership (Factory Class) has a standardized process for acquiring cars (Objects), making it efficient.

Image description

/**
 * The Creator class declares the factory method that is supposed to return an
 * object of a Product class. The Creator's subclasses usually provide the
 * implementation of this method.
 */
abstract class Creator {
    /**
     * Note that the Creator may also provide some default implementation of the
     * factory method.
     */
    public abstract factoryMethod(): Product;

    /**
     * Also note that, despite its name, the Creator's primary responsibility is
     * not creating products. Usually, it contains some core business logic that
     * relies on Product objects, returned by the factory method. Subclasses can
     * indirectly change that business logic by overriding the factory method
     * and returning a different type of product from it.
     */
    public someOperation(): string {
        // Call the factory method to create a Product object.
        const product = this.factoryMethod();
        // Now, use the product.
        return `Creator: The same creator's code has just worked with ${product.operation()}`;
    }
}

/**
 * Concrete Creators override the factory method in order to change the
 * resulting product's type.
 */
class ConcreteCreator1 extends Creator {
    /**
     * Note that the signature of the method still uses the abstract product
     * type, even though the concrete product is actually returned from the
     * method. This way the Creator can stay independent of concrete product
     * classes.
     */
    public factoryMethod(): Product {
        return new ConcreteProduct1();
    }
}

class ConcreteCreator2 extends Creator {
    public factoryMethod(): Product {
        return new ConcreteProduct2();
    }
}

/**
 * The Product interface declares the operations that all concrete products must
 * implement.
 */
interface Product {
    operation(): string;
}

/**
 * Concrete Products provide various implementations of the Product interface.
 */
class ConcreteProduct1 implements Product {
    public operation(): string {
        return '{Result of the ConcreteProduct1}';
    }
}

class ConcreteProduct2 implements Product {
    public operation(): string {
        return '{Result of the ConcreteProduct2}';
    }
}

/**
 * The client code works with an instance of a concrete creator, albeit through
 * its base interface. As long as the client keeps working with the creator via
 * the base interface, you can pass it any creator's subclass.
 */
function clientCode(creator: Creator) {
    // ...
    console.log('Client: I'm not aware of the creator's class, but it still works.');
    console.log(creator.someOperation());
    // ...
}

/**
 * The Application picks a creator's type depending on the configuration or
 * environment.
 */
console.log('App: Launched with the ConcreteCreator1.');
clientCode(new ConcreteCreator1());
console.log('');

console.log('App: Launched with the ConcreteCreator2.');
clientCode(new ConcreteCreator2());

The post Factory Method appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/03/22/factory-method/feed/ 0
Dissecting layered architecture https://prodsens.live/2024/03/08/dissecting-layered-architecture/?utm_source=rss&utm_medium=rss&utm_campaign=dissecting-layered-architecture https://prodsens.live/2024/03/08/dissecting-layered-architecture/#respond Fri, 08 Mar 2024 05:20:59 +0000 https://prodsens.live/2024/03/08/dissecting-layered-architecture/ dissecting-layered-architecture

Selecting the right architecture is key to a project’s success. In software development, architects and developers are always…

The post Dissecting layered architecture appeared first on ProdSens.live.

]]>
dissecting-layered-architecture

Selecting the right architecture is key to a project’s success. In software development, architects and developers are always balancing the needs for scalability, speed, and meeting project goals to create effective solutions. Amidst the myriad of architectural patterns, layered architecture, also known as n-tier architecture, stands out for its structured approach and ease of adaptation. This pattern segregates the application into logical layers, each with a distinct responsibility, thereby promoting a separation of concerns that can lead to more manageable and scalable software designs. Understanding and dissecting layered architecture is crucial for any team aiming to build robust, flexible, and maintainable systems.

Story story…

Story telling GIF
So, in those days in the past Netflix found itself at a crossroads, it had to enter a new era. Gone were the days when their primary business was mailing DVDs; the future was streaming, and with it came a surge of users hungry for on-demand content. However, Netflix’s digital infrastructure, akin to a tangled web code and processes all stuffed into a single, a gigantic box (a monolithic architecture), struggled under the weight of growing demands. This giant box was really slow to adapt, and every small tweak risked toppling the entire operation. In a bold move, Netflix began to dismantle this box, carefully extracting and separating its contents into smaller, specialized boxes, each handling a distinct part of the Netflix experience – strategy known as microservices, which is essentially a nuanced take on layered architecture.

Boom gif
As Amazon’s business boomed, its once robust infrastructure began to buckle under pressure, mirroring challenges faced by Netflix. Overloaded servers and a tangled web of code dependencies threatened to derail the “Everything Store.” Embracing a strategy similar to Netflix, Amazon undertook a significant overhaul of its system architecture, transitioning from a bulky, unified codebase to a sleek arrangement of microservices. This transformation allowed individual teams to update, test, and deploy their services independently, significantly reducing the risk of widespread issues from minor changes. This not only reinforced Amazon’s dominance in e-commerce but also set the stage for the launch of AWS, its cloud computing division. AWS epitomized the benefits of microservices, offering a model for agile, resilient software development. Amazon’s shift to microservices showcases the effectiveness of breaking down complex systems into simpler, interconnected components, ensuring both growth and stability.

What is a layered architecture

Think meme GIF
Layered architecture is like sorting your apps and tasks on your computer into neat folders, each with its own job. It’s a way for software developers to keep things organized when building applications, making sure everything doesn’t get tangled up in a big mess. This method sorts the application into different layers or “levels,” each focusing on specific tasks, such as showing stuff to the user, doing the heavy thinking, or dealing with saving and fetching data. It’s all about keeping things clean and straightforward, so if something needs fixing or updating, you won’t have to dig through a mountain of code.

Think of it as making a cake with different layers. The bottom layer might be your data access layer, handling all the interactions with databases, sort of like making sure the cake has a solid base. The middle layer, the business logic layer, is where all the action happens—mixing ingredients, deciding the cake’s flavor, essentially the brain of the operation. And the top layer is the presentation layer, the icing and decorations, making everything look good and user-friendly.

This setup lets developers change one part without messing up the rest. For instance, if you want to switch from chocolate to vanilla, you don’t need to rebuild the entire cake—just tweak the middle layer. It’s a practical, flexible way to build software, making life easier for the folks coding it and ensuring everything runs smoothly for us, the users. It’s a classic approach that’s stuck around because it works, helping to make complex applications manageable and maintainable.

Why layered architecture

Thinking meme gif
Layered architecture is chosen for several compelling reasons that align with the goals of many software development projects. This architectural style provides a structured approach to designing software applications, offering numerous benefits that address common challenges in software development. Here are the key reasons why layered architecture is widely adopted:

  1. Modularity: By organizing an application into distinct layers, each with its specific responsibility, developers can work on separate parts of the application without impacting others. This modularity makes it easier to manage, develop, and maintain the application.

  2. Flexibility: Changes or updates can be made to one layer without requiring alterations to others. This flexibility is especially beneficial when dealing with changing business requirements or when updating technology stacks.

  3. Scalability: Layered architecture allows for the scaling of individual layers according to their specific load or performance requirements. For instance, the data access layer can be scaled independently to handle increased database load.

  4. Reusability: Components and services within a layer can often be reused in different parts of an application or even in different projects, reducing development time and increasing efficiency.

  5. Maintainability: The separation of concerns facilitated by layered architecture simplifies maintenance and debugging, as issues can be isolated to specific layers. This clear structure also makes it easier for new developers to understand the application.

  6. Improved Security: Security measures can be implemented more effectively in a layered architecture, as each layer can have its own security controls. For example, access control can be enforced in the presentation layer, while data integrity checks can be implemented in the business logic layer.

  7. Testability: Each layer can be tested independently, which simplifies the testing process. Mock objects or stubs can be used to simulate the behavior of adjacent layers, making unit testing and integration testing more straightforward.

The layers

Layers of Layered
Some important sections in layered architecture include:

Presentation Layer

Presentation layer
The Presentation Layer is essentially the face of your application, the part that users interact with. Imagine it as the front desk of a hotel, where the interaction between guests and the hotel’s services begins. This layer’s primary job is to present information in a user-friendly manner and interpret the user’s inputs, turning clicks, taps, and types into actions the application can understand and respond to. It’s all about creating an intuitive, engaging user experience, whether it’s through a sleek website interface, a responsive mobile app, or a powerful desktop application.

Underneath the hood, the Presentation Layer is busy translating user requests to the Business Logic Layer and then displaying the results back to the user. It ensures that data is presented in a clear, accessible way, handling everything from formatting data to managing how users navigate through the app. By focusing on user interaction, it plays a crucial role in making the software accessible and enjoyable to use, directly influencing user satisfaction and engagement. It’s the bridge between the human user and the complex processes running in the background, making sure the digital conversation goes smoothly.

Business Layer

Business Layer
The Business Layer is essentially the brain of any software application, quietly sitting between the user’s touchpoints and the nitty-gritty of data management. It’s like the director behind a play, making sure every scene flows smoothly into the next, according to the script. Here, all the heavy lifting happens: validating the user’s inputs, crunching numbers, making key decisions, and seamlessly coordinating data flow. This is where the application’s heartbeat is, ensuring everything ticks according to the business’s rules and goals.

Think of it as the middleman who speaks both the language of the user’s wishes and the technical dialect of database interactions. It’s incredibly versatile, capable of adapting to new business strategies or rules without causing a ripple effect across the user interface or storage systems. This layer keeps the app’s logic tidy and centralized, making updates a breeze and ensuring the whole operation runs without a hitch. It’s the unsung hero that works tirelessly behind the scenes, ensuring the app not only meets the immediate needs of its users but also stays aligned with the long-term vision of the business. In short, the Business Layer is where the magic happens, turning user interactions into real outcomes.

Persistence Layer

Persistence Layer
The Persistence Layer is where all the data action happens, acting as the sturdy foundation of any app. Imagine it as the backstage crew of a theater, working tirelessly to make sure every prop is in place, ready for the next scene. This layer’s job is to manage how data is stored, retrieved, and updated in databases or other storage systems. It’s the bridge between the application’s working memory and its long-term memory, ensuring that data doesn’t just vanish when the app closes.

In simpler terms, it’s like a librarian who knows exactly where every book should go, keeps track of which ones are checked out, and makes sure they’re all in the right place at the end of the day. It allows the rest of the application to forget about the complexities of how data is stored or the nitty-gritty of database languages. Developers can ask for what they need using simple commands, and the Persistence Layer takes care of the rest, whether it’s saving new data, fetching information for a user, or updating an existing record.

This layer is crucial for maintaining the integrity and performance of the app’s data interactions, making sure that data is not only stored safely but also easily accessible when needed. It’s the unsung hero that ensures the digital world remains orderly and efficient, enabling seamless experiences for users and peace of mind for developers knowing that their data is handled with care.

Types of Layered architectures

Layered architecture, a fundamental principle in software design, organizes applications into distinct levels, each with its unique role. This design pattern enhances maintainability, scalability, and allows for easier debugging and updates. While the concept is universal, the implementation can vary, leading to different types of layered architectures, each tailored to specific project needs or preferences.

Traditional Layered Architecture

Traditional architecture

Traditional Layered Architecture is the classic model, typically consisting of the Presentation, Business, and Data Access layers. It’s akin to a well-organized filing system, where each drawer holds a specific type of document. This straightforward approach facilitates a clear separation of concerns, making it easier for developers to locate and manage code. Ideal for small to medium-sized applications, it ensures that each component focuses on its specific task, from handling user interfaces to managing database interactions.

Three-tier Architecture

Three-tier Architecture

Three-tier Architecture is a direct implementation of layered design, dividing the application into three distinct tiers: the Presentation tier for user interfaces, the Logic tier for processing, and the Data tier for database management. It’s like a three-layer cake, with each layer having its unique flavor and purpose. This model is particularly popular for web applications, offering a balanced approach to separating concerns, which simplifies development, maintenance, and scaling.

N-tier/Multi-tier Architecture

N-Tier Architecture
N-tier or Multi-tier Architecture expands on the three-tier model by introducing additional layers, such as a Service layer or an Integration layer. Imagine adding more floors to a building, each designed for a different purpose like administration, sales, or customer service. This approach offers flexibility and scalability, accommodating complex applications and systems that require more nuanced separation of concerns. It’s particularly useful for enterprise-level applications that need to integrate with various databases, services, and external applications.

Microservices Architecture

Microservices Architecture
Microservices Architecture takes the principles of layered architecture and applies them across distributed systems. Each microservice acts as an independent layer, responsible for a specific business function and communicating with other services via well-defined interfaces. Think of it as a community of small, specialized shops rather than one big department store. This architecture supports agile development practices, allows for easy scaling, and can significantly enhance the resilience and flexibility of large, complex systems.

Advantages and disadvantages of layered architecture

red vs blue pills
| Advantages | Disadvantages |
|————————————|————————————-|
| Modularity
Organizes an application into distinct layers, making it easier to manage and develop. | Performance Overhead
The additional layers can introduce latency, especially if many layers are involved. |
| Flexibility
Updates can be made to one layer without requiring alterations to others, adapting easily to changes. | Increased Complexity
While it simplifies development within a layer, the overall architecture can become more complex. |
| Scalability
Individual layers can be scaled according to their specific needs, enhancing the application’s performance. | Redundant Data Access
Can lead to inefficiencies due to multiple layers performing similar data access logic. |
| Reusability
Components within a layer can often be reused in different parts of an application or even across projects. | Rigidity
If not well-designed, the architecture can become too rigid, making it difficult to adapt to new requirements. |
| Maintainability
Simplifies maintenance and debugging, as issues can be isolated within specific layers. | Dependency Issues
Layers depend on the functionality of layers below them, which can complicate updates or changes. |
| Improved Security
Enables effective implementation of security measures at different layers. | Over-Abstraction
Excessive use of abstraction can lead to a bloated codebase that’s hard to navigate. |
| Testability
Facilitates easier testing, as each layer can be tested independently. | |

Best practices for layered architecture

thumb up gif
Implementing a layered architecture effectively requires adherence to best practices that ensure the architecture serves its intended purpose—making the application scalable, maintainable, and flexible. Here are some of the best practices for deploying a layered architecture:

  1. Clearly Define Layer Responsibilities: Each layer should have a clear and distinct responsibility. Avoid mixing different types of logic in the same layer to maintain a clean separation of concerns.

  2. Minimize Dependencies Between Layers: Depend on abstractions rather than concrete implementations to minimize tight coupling between layers. This approach facilitates easier changes and testing.

  3. Use Dependency Injection: Implement dependency injection to decouple the layers further. This allows for better testing capabilities and adherence to the inversion of control (IoC) principle.

  4. Implement a Strong Contract Between Layers: Define clear interfaces or contracts between layers. This ensures that changes within one layer do not adversely affect others.

  5. Isolate External Dependencies: External dependencies (such as third-party libraries or APIs) should be encapsulated within their own layer or set of components to prevent them from leaking into the business logic.

  6. Design for Reusability: Where possible, design components within a layer to be reusable across the application. This reduces duplication and fosters a DRY (Don’t Repeat Yourself) codebase.

  7. Leverage Intermediary Layers for Cross-Cutting Concerns: Utilize layers or components specifically for handling cross-cutting concerns such as logging, authentication, and error handling to avoid code duplication and ensure consistency.

  8. Keep the Business Logic Layer Independent: The business logic layer should be kept independent of the user interface and data storage mechanisms. This allows the core functionality of the application to remain consistent regardless of changes in the presentation or data access layers.

  9. Ensure Scalability of Individual Layers: Design each layer with scalability in mind. For instance, the data access layer should be able to handle increased load without affecting the business logic or presentation layers.

  10. Prioritize Performance Optimization: While layer separation is crucial, be mindful of the performance implications. Optimize the interactions between layers to minimize overhead and consider asynchronous processing where appropriate.

  11. Document the Architecture: Maintain up-to-date documentation on the architecture, including the responsibilities of each layer, the data flow, and the interfaces between layers. This is crucial for onboarding new team members and facilitating maintenance.

Wrap up

Layered architecture organizes software into distinct levels, each handling specific responsibilities, from user interfaces (Presentation Layer) to core logic (Business Layer) and data management (Data Access Layer). This structure supports modularity, making applications easier to manage, update, and scale by isolating changes to specific layers. Popular variations include the traditional three-tier model, focusing on presentation, logic, and data, and the more flexible N-tier architecture, which adds additional layers as needed. Microservices architecture, though more distributed, shares the principle of dividing responsibilities for greater agility and scalability. Adopting layered architecture offers benefits like improved maintainability, reusability, and security, but it can introduce performance overhead and complexity. Best practices such as defining clear layer responsibilities, minimizing dependencies, and using dependency injection ensure the architecture enhances the application’s development and maintenance. Layered architecture remains a popular choice for structuring complex applications, providing a balance between organization and flexibility.

Conclusion

Bye meme gif
So, wrapping things up, the whole layered architecture thing is pretty much like organizing your digital workspace into clear, easy-to-find sections. It’s a smart way to build software, ensuring everything from the user’s first click to the nitty-gritty data stuff is neatly separated and easy to handle. This approach makes apps way easier to tweak, scale, and keep running smoothly, even as they grow or need to change with the times. Sure, it’s not perfect—sometimes it can slow things down or make things a bit complex—but with a little care and following some smart tips, these issues can be mostly smoothed out. Whether it’s sticking to the basics with a three-tier setup or going all out with N-tier or microservices, sticking to this game plan helps build solid, flexible software. As tech keeps racing ahead, the good old principles of layered architecture will keep being a go-to for creating software that’s not just good for now, but also ready for whatever comes next.

The post Dissecting layered architecture appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/03/08/dissecting-layered-architecture/feed/ 0
From Converters to Dependency Injection: Navigating Model Migrations https://prodsens.live/2023/09/29/from-converters-to-dependency-injection-navigating-model-migrations/?utm_source=rss&utm_medium=rss&utm_campaign=from-converters-to-dependency-injection-navigating-model-migrations https://prodsens.live/2023/09/29/from-converters-to-dependency-injection-navigating-model-migrations/#respond Fri, 29 Sep 2023 15:25:24 +0000 https://prodsens.live/2023/09/29/from-converters-to-dependency-injection-navigating-model-migrations/ from-converters-to-dependency-injection:-navigating-model-migrations

Do you know the three-layer software design? Think of it as tech’s equivalent of a sandwich with neatly…

The post From Converters to Dependency Injection: Navigating Model Migrations appeared first on ProdSens.live.

]]>
from-converters-to-dependency-injection:-navigating-model-migrations

Do you know the three-layer software design? Think of it as tech’s equivalent of a sandwich with neatly stacked presentation, logic, and data layers. Now, why choose multiple models over one mammoth model? It’s akin to organizing playlists by mood. Nifty, isn’t it? In the coding realm, it’s about being spick and span. Tiny model tweaks feel more like a brisk walk than a marathon.

But the real query is: how do we flit between models within these layers, especially when models diverge or beckon another repo or service?

Well, Examples Speak Louder

Keep It Simple

Suppose a Dog is set to evolve into a DogDto.

public class Dog
{
    public string Name { get; set; }
}

public class DogDto
{
    public string Name { get; set; }
}

Seems direct, right? Either use AutoMapper:

Dog dog = new Dog{ Name = "doge"};
DogDto dogDto = _mapper.Map<DogDto>(dog);

Or, take the traditional route:

Dog dog = new Dog{ Name = "doge"};
DogDto dogDto = new DogDto { Name = dog.Name };

What if DogDto opts for a different nomenclature?

public class Dog
{
    public string Name { get; set; }
}

public class DogDto
{
    public string Naming { get; set; }
}

You’ve got two aces up your sleeve: use a FromXX/ToXX method or integrate a mapper profile. Here’s option one:

public class DogDto
{
    public string Naming { get; set; }

    public static DogDto FromDog(Dog dog)
    {
        return new DogDto
        {
            Naming = dog.Name
        };
    }
}

And here you have how would mapper profile look like:

using AutoMapper;

public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<Dog, DogDto>()
            .ForMember(dest => dest.Naming, opt => opt.MapFrom(src => src.Name));
    }
}
var configuration = new MapperConfiguration(cfg =>
{
     cfg.AddProfile<UserProfile>();
});

When Models are Like Apples and Oranges

public class Dog
{
    public string Name { get; set; }
}

public class DogDto
{
    public int NumberOfLegs { get; set; }

    public Superpower SuperPower  { get; set; }
}

Time to roll up the sleeves! Converters are the knights in shining armor here:

public interface IConverter<In, Out>
{
    Out Convert(In input);
}

public class DogConverter : IConverter<Dog, DogDto>
{
    // Your conversion magic here!
}

Elevate Your Game: Services Within Converters

Ever bumped into a situation where your models are as different as, well, apples and oranges? And, to spice things up, you need to juggle an AnimalRepository or a FoodService within your converter. Tough spot, right?

Remember this code?

var converter = new DogConverter(_animalRepository, _foodService, _anotherService, _pleaseStopThisIsTooMuch, userId);

It might remind you of the infamous spaghetti mess. But fear not, there’s a cleaner approach. Let me share a neat trick I’ve brewed up!

Step into the World of ConverterFactory

A ConverterFactory is like a genie granting your converter wishes.

public class ConverterFactory
{
    private readonly IServiceProvider _serviceProvider;

    public ConverterFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IConverter<InType, OutType> GetConverter<InType, OutType>()
    {
        var converter = _serviceProvider.GetService(typeof(IConverter<InType, OutType>));
        if (converter == null)
        {
            throw new Exception("Missing converter alert!");
        }

        return (IConverter<InType, OutType>)converter;
    }
}

Boot Things Up in Startup.cs

Here’s where we align our forces. Add your services to the service collection:

services.AddTransient<ConverterFactory>();
services.AddTransient<IConverter<Dog, DogDto>, DogConverter>();
services.AddTransient<IAnimalRepository, AnimalRepository>();
services.AddTransient<IFoodService, FoodService>();

Smooth Operations with DogHelper

With everything set, it’s showtime:

public class DogHelper
{
    private readonly ConverterFactory _converterFactory;
    public DogHelper(ConverterFactory converterFactory)
    {
        _converterFactory = converterFactory;
    }

///...
    public DogDto TransformDog(Dog input)
    {
        var converter = _converterFactory.GetConverter<Dog, DogDto>();
        return converter.Convert(input);
    }
}

What’s the magic? No more manually creating converter instances and wrestling with services. Dependency injection sweeps in, handling all the heavy lifting ensuring cleaner and more efficient code.

Wrapping Up: A Fresh Look at Tools and Tactics

Is Automapper Outdated? Let’s Debate

Let me drop a bombshell: Could Automapper be sliding into obsolescence? I recently found myself locked in a spirited dialogue with a colleague about its continuing relevance. The crux of the debate? Automapper’s Achilles’ heel is its runtime error reporting. Get a property name or type conversion wrong, and you’re signing up for bug city.

Sure, Automapper shone when replicating models that were virtually identical, especially those property-heavy behemoths we often see in enterprise settings. But let’s be real: Visual Studio’s ultra-efficient auto-code completion has redefined the game, making that argument a little wobbly.

ConverterFactory: A Quick Fix, But Not a Cure-All

Time to tackle the not-so-hidden issue: If you find yourself leaning heavily on something like a ConverterFactory, coupled with dependency injection, it might be time to sniff out potential architectural odors. Could it be a red flag suggesting you revisit your system’s blueprints?

The Final Takeaway

So, there we are! Whether you’re nodding along or vigorously shaking your head, my hope is that this exploration has sparked some intellectual kindling. Got a different approach? Differing opinions? Lay it on me. Let’s remember that achieving perfection is less a destination and more an ever-evolving journey. There’s always room for development and refinement in the ever-changing tech landscape. So go ahead, share your insights—I’m more than ready for a good round to disscuss!

The post From Converters to Dependency Injection: Navigating Model Migrations appeared first on ProdSens.live.

]]>
https://prodsens.live/2023/09/29/from-converters-to-dependency-injection-navigating-model-migrations/feed/ 0
Single Source of Truth https://prodsens.live/2023/08/02/single-source-of-truth/?utm_source=rss&utm_medium=rss&utm_campaign=single-source-of-truth https://prodsens.live/2023/08/02/single-source-of-truth/#respond Wed, 02 Aug 2023 10:25:48 +0000 https://prodsens.live/2023/08/02/single-source-of-truth/ single-source-of-truth

1. The Principle of Single Source of Truth (SPOT) The SPOT, or Single Point of Truth, principle is…

The post Single Source of Truth appeared first on ProdSens.live.

]]>
single-source-of-truth

1. The Principle of Single Source of Truth (SPOT)

The SPOT, or Single Point of Truth, principle is a better and more semantically-focused interpretation of DRY (Don’t Repeat Yourself). The principle suggests that for any given piece of information, there should only be one authoritative location where that data is defined or set. For instance, if there’s a rule that pizzas need at least one topping, there should be a single place in the code where that condition is articulated. By maintaining a single source of truth, it ensures that when changes need to be made, they aren’t sporadically made in one place but not the others.

2. The Significance of Code Locality

A related concept to SPOT is code locality. Code locality refers to the organization of related code segments, with the goal of enabling humans to discover and remember related code easily. The idea is to keep related elements as close together as possible in the code, especially when multiple sources of truth are necessary. This organization strategy aids in code comprehension and enhances the efficiency of tracking and implementing changes in the codebase.

3. Spatial Awareness in Code

The saying, “if things tend to change together, they should be closer together,” also aligns with the principle of code locality. Structuring the code in a way that elements likely to change simultaneously are placed together can significantly improve code maintainability. This approach, in contrast to using layers as a primary organizational method, prevents scattering related elements across different top-level directories.

4. Simple Over Complex Structures

Another aspect of good coding practice that ties in with the SPOT principle is the preference for simpler structures. When structuring the codebase, linear arrangements, or ‘lists,’ are often more favorable than tree or graph structures. They are easier to understand, and they help prevent unnecessarily complex and convoluted code that could lead to difficulties in maintenance and comprehension.

I’m building a game about software development, incorporating real principles that have proven their usefulness throughout my 20+ years of career experience.

I’m looking for feedback from Alpha version – try game now.

5. The Principle of Least Power

The principle of least power could be seen as an overarching guideline to the practices described above. This principle suggests that the simplest solution capable of effectively solving a problem is usually the best choice. As applied to code structures, while the graph data structure is highly versatile, it should only be used when simpler data structures fall short, minimizing complexity.

6. The Advantage of Flat Data Over Tree Data

While flat data are simpler to handle than tree data, the real distinction comes when processing the data. The reason for this is that trees invite recursion, and as the complexity of the logic increases, it can be increasingly difficult to understand what’s going on. Conversely, processing lists iteratively can be less complex, and refactoring recursive code to iterative code can often reveal or solve hidden bugs.

7. Understanding the Balance

As a junior developer, understanding the balance between too much and too little abstraction is key. While the SPOT principle and related concepts can greatly aid in creating maintainable, efficient code, it’s essential to know when to apply these principles. Overcomplicating a simple problem with unnecessary abstractions or prematurely refactoring can be as detrimental as not applying the principles at all. Ultimately, the goal is to write code that effectively addresses the core problem, while also being easy to maintain and understand.

Follow me on X(ex-Twitter)

The post Single Source of Truth appeared first on ProdSens.live.

]]>
https://prodsens.live/2023/08/02/single-source-of-truth/feed/ 0
Design Pattern: Strategy (TS) https://prodsens.live/2023/04/04/design-pattern-strategy-ts/?utm_source=rss&utm_medium=rss&utm_campaign=design-pattern-strategy-ts https://prodsens.live/2023/04/04/design-pattern-strategy-ts/#respond Tue, 04 Apr 2023 23:02:43 +0000 https://prodsens.live/2023/04/04/design-pattern-strategy-ts/ design-pattern:-strategy-(ts)

The strategy pattern is useful in cases where we have multiple algorithms or strategies that can be interchangeable,…

The post Design Pattern: Strategy (TS) appeared first on ProdSens.live.

]]>
design-pattern:-strategy-(ts)

The strategy pattern is useful in cases where we have multiple algorithms or strategies that can be interchangeable, and we want to encapsulate them behind a common interface. This allows us to easily switch between different strategies without changing the client code, and also promotes separation of concerns by keeping the algorithms isolated in their own classes.

This pattern can be particularly useful in scenarios where the behavior of an object needs to vary dynamically based on different conditions or inputs. It provides a flexible and extensible way to encapsulate different algorithms or strategies and allows for easy customization and modification without modifying the core logic of the client code.

interface SortStrategy {
  sort(data: number[]): number[];
}

class BubbleSortStrategy implements SortStrategy {
  sort(data: number[]): number[] {
    // Implementation of bubble sort algorithm
    console.log("Sorting using bubble sort strategy");
    // ...
    return data;
  }
}

class QuickSortStrategy implements SortStrategy {
  sort(data: number[]): number[] {
    // Implementation of quick sort algorithm
    console.log("Sorting using quick sort strategy");
    // ...
    return data;
  }
}

class SortContext {
  private strategy: SortStrategy;

  constructor(strategy: SortStrategy) {
    this.strategy = strategy;
  }

  setStrategy(strategy: SortStrategy): void {
    this.strategy = strategy;
  }

  sort(data: number[]): number[] {
    return this.strategy.sort(data);
  }
}

// Usage example
const data = [5, 2, 9, 1, 5, 6];
const bubbleSort = new BubbleSortStrategy();
const quickSort = new QuickSortStrategy();

const context = new SortContext(bubbleSort);
context.sort(data); // Sorting using bubble sort strategy

context.setStrategy(quickSort);
context.sort(data); // Sorting using quick sort strategy

In this example, the strategy pattern is used to encapsulate different sorting algorithms (bubble sort and quick sort) behind a common SortStrategy interface, and the client can switch between them dynamically at runtime using the SortContext class. This provides flexibility and extensibility in choosing different sorting strategies without changing the core logic of the client code. It also promotes separation of concerns by keeping the sorting algorithms isolated in their own classes, making the code more maintainable and testable.

The strategy pattern is commonly used in situations where different algorithms need to be applied to the same problem or input data, and the choice of algorithm needs to be made at runtime based on specific conditions or configuration settings.

The post Design Pattern: Strategy (TS) appeared first on ProdSens.live.

]]>
https://prodsens.live/2023/04/04/design-pattern-strategy-ts/feed/ 0