SlideShare una empresa de Scribd logo
1 de 49
Descargar para leer sin conexión
@rafaoe !
@JavierCane "
⛑ Aprende a convivir con el
monolito legacy
(o no)
Volumen II
Todo lo que veréis en esta
charla HA PASADO
Los hechos, nombres, y tips
son semi-REALES
En algunos tips
esta charla podría
HERIR SENSIBILIDADES
Compartimos
aprendizajes reales
La realidad y el legacy no
entienden de sensibilidades
@rafaoe @JavierCane
@rafaoe @JavierCane
$
Comencem
Ey, ey.
Hay código… pero no hay test
UserController.java
package com.codely.controller;
import com.codely.model.User;
import com.codely.repository.UserRepository;
import com.codely.service.CacheService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable("id") Long id) {
UserController.java
@RequestMapping("/user")
public class UserController {
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable("id") Long id) {
CacheService cacheService = CacheService.getInstance();
String cacheKey = "user_" + id;
User user = cacheService.get(cacheKey, User.class);
if (user != null) {
return new ResponseEntity<>(user, HttpStatus.OK);
}
UserRepository userRepository = UserRepository.getInstance();
Optional<User> userOptional = userRepository.findById(id);
if (userOptional.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
user = userOptional.get();
cacheService.put(cacheKey, user);
@RequestMapping("/user")
public class UserController {
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable("id") Long id) {
CacheService cacheService = CacheService.getInstance();
String cacheKey = "user_" + id;
User user = cacheService.get(cacheKey, User.class);
if (user != null) {
return new ResponseEntity<>(user, HttpStatus.OK);
}
UserRepository userRepository = UserRepository.getInstance();
Optional<User> userOptional = userRepository.findById(id);
if (userOptional.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
user = userOptional.get();
cacheService.put(cacheKey, user);
%
%
UserController.java
Flujo de una petición
&
Infraestructura
Aplicación
Dominio
Flujo de una petición
&
Unit test Integration test
I
A
D
Repository
Controller UseCase
Service
Implementation
Acceptance test
Flujo de una petición
&
Repository
Controller UseCase
Service
Unit test Integration test
Implementation
I
A
D
Acceptance test
'
(
)
Flujo de una petición
&
Repository
Controller UseCase
Service
Unit test Integration test
Implementation
Acceptance test
I
A
D
*
Flujo de una petición
&
Repository
Controller UseCase
Service
Unit test Integration test
Implementation
Acceptance test
I
A
D
$
Aprendizajes
⏩
, No hay tests → Aceptación
⬇ Dump diario de la BD de prod
. Dump selectivo
/ Datos al vuelo
bit.ly/commitconf23
⏩ Pasan 3 años
Primera tarea en lo nuevo
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM score
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM score
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM score
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM score
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM score
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM score
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRank
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY t.country_id
ORDER BY t.highScore DESC) AS countryRank,
RANK() OVER (ORDER BY t.highScore DESC) AS globalRan
FROM (
SELECT
user_id,
country_id,
MAX(score) AS highScore
FROM (
SELECT
*
FROM (
SELECT
t.user_id,
t.country_id,
t.highScore,
RANK() OVER (PARTITION BY
ORDER BY t.highScore
RANK() OVER (ORDER BY t.h
FROM (
SELECT
user_id,
country_id,
MAX(score) AS hi
FROM score
WHERE topic_id = 101
GROUP BY user_id, co
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
)
WHERE topic_id = 101
GROUP BY user_id, country_id
) AS t
) AS rt
WHERE
rt.user_id = ?
Flujo de una petición
&
Repository
Controller UseCase
Service
Unit test Integration test
Implementation
Acceptance test
I
A
D
*
Flujo de una petición
&
Repository
Controller UseCase
Service
Unit test Integration test
Implementation
Acceptance test
I
A
D
TopGamesRepository
interface TopGamesRepository {
public save(TopGame[] games): void;
public searchAll(): TopGame[];
}
Cómo hacer que sea un éxito
Ey, ey.
He gamificado la migración
MySqlRepository.php
abstract class MySqlRepository {
public function search($query, $tag) {
$taggedQuery = "/*"!999999 $tag */$ $query";
// ...
}
}
OtherSqlRepository.php
abstract class OtherSqlRepository {
public function search($query, $tag) {
$taggedQuery = "IF('$tag' = '', 1, 0) AS tagged $query";
// ...
}
}
⏩ Pasan 10 años
0
1
Evolución tamaño instancias EC2
2
r6a.metal
r6a.48xlarge
r6a.48large
r6a.large r6a.xlarge
4
5
♻
7
8
Sincronización entre sistema legacy y el nuevo
♻
Legacy New
TubiAPIradora
legacy
domain_events
new
replicate_from_legacy
new
domain_events
legacy
replicate_from_new
Publica evento
Replica
Consume
Publica evento
Replica
Consume
Invoca API
9
¿Fantasía en los nombres de proyecto?
☝
Sí No
TubiAPIradora vs CDC
☝
Change Data Capture Pattern
Consume
Trigger BD
TubiAPIradora
bit.ly/codely-eda
TubiAPIradora vs CDC
☝
Change Data Capture Pattern
Consume
Trigger BD
TubiAPIradora
bit.ly/codely-eda
Cómo hacer que sea un éxito
LegacyEventBus
public publish(array $event): void
{
Publisher.publish(
add(
$event,
['metadata' =>& ['from_legacy' =>& true]]
)
);
}
Aprende a convivir con el
monolito legacy (o no)
Aprende a convivir con el
monolito legacy
Resultados encuesta
¿Nombres fantasiosos para proyectos?
2
0
25
50
75
100
0
100
No Sí
bit.ly/hiring-codely
¡Muchas gracias!
Nos vemos en Codely

Más contenido relacionado

Similar a Legacy monolito y rankings de puntuaciones

05 sentencias basicas
05 sentencias basicas05 sentencias basicas
05 sentencias basicasCarlos
 
Linq
LinqLinq
Linqblo85
 
Linq
LinqLinq
Linqblo85
 
Análisis de Datos con MongoDB
Análisis de Datos con MongoDBAnálisis de Datos con MongoDB
Análisis de Datos con MongoDBAlejandro Mancilla
 
Desarrollando aplicaciones web usando Catalyst y jQuery
Desarrollando aplicaciones web usando Catalyst y jQueryDesarrollando aplicaciones web usando Catalyst y jQuery
Desarrollando aplicaciones web usando Catalyst y jQueryJavier P.
 
Ejercicios de programación en C (Estructuras condicionales-Selectivas)
Ejercicios de programación en C (Estructuras condicionales-Selectivas)Ejercicios de programación en C (Estructuras condicionales-Selectivas)
Ejercicios de programación en C (Estructuras condicionales-Selectivas)Maynor Mendoza
 
Comandos de Raptor, Java y C# Sharp
Comandos de Raptor, Java y C# SharpComandos de Raptor, Java y C# Sharp
Comandos de Raptor, Java y C# SharpAna Ruth G H
 
Manual de App
Manual de App Manual de App
Manual de App valeria-lg
 
Mejoras en T-SQL para SQL Server 2005
Mejoras en T-SQL para SQL Server 2005Mejoras en T-SQL para SQL Server 2005
Mejoras en T-SQL para SQL Server 2005pabloesp
 
Presentación programacion
Presentación programacionPresentación programacion
Presentación programacionMiguel Cajiga
 
Desarrollo de aplicaciones web usando Catalyst y jQuery
Desarrollo de aplicaciones web usando Catalyst y jQueryDesarrollo de aplicaciones web usando Catalyst y jQuery
Desarrollo de aplicaciones web usando Catalyst y jQueryJavier P.
 

Similar a Legacy monolito y rankings de puntuaciones (20)

Comandos
ComandosComandos
Comandos
 
05 sentencias basicas
05 sentencias basicas05 sentencias basicas
05 sentencias basicas
 
Linq
LinqLinq
Linq
 
Linq
LinqLinq
Linq
 
Linq
LinqLinq
Linq
 
Integración de DataStax de Spark con Cassandra
Integración de DataStax de Spark con CassandraIntegración de DataStax de Spark con Cassandra
Integración de DataStax de Spark con Cassandra
 
Lista de Comandos
Lista de ComandosLista de Comandos
Lista de Comandos
 
Análisis de Datos con MongoDB
Análisis de Datos con MongoDBAnálisis de Datos con MongoDB
Análisis de Datos con MongoDB
 
Metricas del proyecto
Metricas del proyectoMetricas del proyecto
Metricas del proyecto
 
Consultas sparql
Consultas sparqlConsultas sparql
Consultas sparql
 
Lenguaje C
Lenguaje CLenguaje C
Lenguaje C
 
Algoritmos resueltos
Algoritmos resueltosAlgoritmos resueltos
Algoritmos resueltos
 
Desarrollando aplicaciones web usando Catalyst y jQuery
Desarrollando aplicaciones web usando Catalyst y jQueryDesarrollando aplicaciones web usando Catalyst y jQuery
Desarrollando aplicaciones web usando Catalyst y jQuery
 
Ejercicios de programación en C (Estructuras condicionales-Selectivas)
Ejercicios de programación en C (Estructuras condicionales-Selectivas)Ejercicios de programación en C (Estructuras condicionales-Selectivas)
Ejercicios de programación en C (Estructuras condicionales-Selectivas)
 
Programar en c
Programar en cProgramar en c
Programar en c
 
Comandos de Raptor, Java y C# Sharp
Comandos de Raptor, Java y C# SharpComandos de Raptor, Java y C# Sharp
Comandos de Raptor, Java y C# Sharp
 
Manual de App
Manual de App Manual de App
Manual de App
 
Mejoras en T-SQL para SQL Server 2005
Mejoras en T-SQL para SQL Server 2005Mejoras en T-SQL para SQL Server 2005
Mejoras en T-SQL para SQL Server 2005
 
Presentación programacion
Presentación programacionPresentación programacion
Presentación programacion
 
Desarrollo de aplicaciones web usando Catalyst y jQuery
Desarrollo de aplicaciones web usando Catalyst y jQueryDesarrollo de aplicaciones web usando Catalyst y jQuery
Desarrollo de aplicaciones web usando Catalyst y jQuery
 

Más de CodelyTV

Back to the basics: Modelando nuestro dominio #scbcn19
Back to the basics: Modelando nuestro dominio #scbcn19Back to the basics: Modelando nuestro dominio #scbcn19
Back to the basics: Modelando nuestro dominio #scbcn19CodelyTV
 
Microservices: Improving the autonomy of our teams with Event-Driven Architec...
Microservices: Improving the autonomy of our teams with Event-Driven Architec...Microservices: Improving the autonomy of our teams with Event-Driven Architec...
Microservices: Improving the autonomy of our teams with Event-Driven Architec...CodelyTV
 
Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...
Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...
Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...CodelyTV
 
Towards Functional Programming through Hexagonal Architecture
Towards Functional Programming through Hexagonal ArchitectureTowards Functional Programming through Hexagonal Architecture
Towards Functional Programming through Hexagonal ArchitectureCodelyTV
 
Avoiding the domino effect in our [micro]services (SOLID at macro-design level)
Avoiding the domino effect in our [micro]services (SOLID at macro-design level)Avoiding the domino effect in our [micro]services (SOLID at macro-design level)
Avoiding the domino effect in our [micro]services (SOLID at macro-design level)CodelyTV
 
From framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytvFrom framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytvCodelyTV
 

Más de CodelyTV (6)

Back to the basics: Modelando nuestro dominio #scbcn19
Back to the basics: Modelando nuestro dominio #scbcn19Back to the basics: Modelando nuestro dominio #scbcn19
Back to the basics: Modelando nuestro dominio #scbcn19
 
Microservices: Improving the autonomy of our teams with Event-Driven Architec...
Microservices: Improving the autonomy of our teams with Event-Driven Architec...Microservices: Improving the autonomy of our teams with Event-Driven Architec...
Microservices: Improving the autonomy of our teams with Event-Driven Architec...
 
Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...
Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...
Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...
 
Towards Functional Programming through Hexagonal Architecture
Towards Functional Programming through Hexagonal ArchitectureTowards Functional Programming through Hexagonal Architecture
Towards Functional Programming through Hexagonal Architecture
 
Avoiding the domino effect in our [micro]services (SOLID at macro-design level)
Avoiding the domino effect in our [micro]services (SOLID at macro-design level)Avoiding the domino effect in our [micro]services (SOLID at macro-design level)
Avoiding the domino effect in our [micro]services (SOLID at macro-design level)
 
From framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytvFrom framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytv
 

Último

Caso de éxito de Hervian con el ERP Sage 200
Caso de éxito de Hervian con el ERP Sage 200Caso de éxito de Hervian con el ERP Sage 200
Caso de éxito de Hervian con el ERP Sage 200Opentix
 
Segmentacion Segmantica_Modelos UNET and DEEPLABV3
Segmentacion Segmantica_Modelos UNET and DEEPLABV3Segmentacion Segmantica_Modelos UNET and DEEPLABV3
Segmentacion Segmantica_Modelos UNET and DEEPLABV3AlexysCaytanoMelndez1
 
Manual de Usuario APPs_AppInventor-2023.pdf
Manual de Usuario APPs_AppInventor-2023.pdfManual de Usuario APPs_AppInventor-2023.pdf
Manual de Usuario APPs_AppInventor-2023.pdfmasogeis
 
BREEAM ES Urbanismo como herramienta para un planeamiento sostenible - Miguel...
BREEAM ES Urbanismo como herramienta para un planeamiento sostenible - Miguel...BREEAM ES Urbanismo como herramienta para un planeamiento sostenible - Miguel...
BREEAM ES Urbanismo como herramienta para un planeamiento sostenible - Miguel...ITeC Instituto Tecnología Construcción
 
Introducción a Funciones LENGUAJE DART FLUTTER
Introducción a Funciones LENGUAJE DART FLUTTERIntroducción a Funciones LENGUAJE DART FLUTTER
Introducción a Funciones LENGUAJE DART FLUTTEREMMAFLORESCARMONA
 
PARTES DEL TECLADO Y SUS FUNCIONES - EJEMPLO
PARTES DEL TECLADO Y SUS FUNCIONES - EJEMPLOPARTES DEL TECLADO Y SUS FUNCIONES - EJEMPLO
PARTES DEL TECLADO Y SUS FUNCIONES - EJEMPLOSelenaCoronadoHuaman
 
Unidad_3_T1_AutomatasFinitos presentacion
Unidad_3_T1_AutomatasFinitos presentacionUnidad_3_T1_AutomatasFinitos presentacion
Unidad_3_T1_AutomatasFinitos presentacionarmando_cardenas
 

Último (7)

Caso de éxito de Hervian con el ERP Sage 200
Caso de éxito de Hervian con el ERP Sage 200Caso de éxito de Hervian con el ERP Sage 200
Caso de éxito de Hervian con el ERP Sage 200
 
Segmentacion Segmantica_Modelos UNET and DEEPLABV3
Segmentacion Segmantica_Modelos UNET and DEEPLABV3Segmentacion Segmantica_Modelos UNET and DEEPLABV3
Segmentacion Segmantica_Modelos UNET and DEEPLABV3
 
Manual de Usuario APPs_AppInventor-2023.pdf
Manual de Usuario APPs_AppInventor-2023.pdfManual de Usuario APPs_AppInventor-2023.pdf
Manual de Usuario APPs_AppInventor-2023.pdf
 
BREEAM ES Urbanismo como herramienta para un planeamiento sostenible - Miguel...
BREEAM ES Urbanismo como herramienta para un planeamiento sostenible - Miguel...BREEAM ES Urbanismo como herramienta para un planeamiento sostenible - Miguel...
BREEAM ES Urbanismo como herramienta para un planeamiento sostenible - Miguel...
 
Introducción a Funciones LENGUAJE DART FLUTTER
Introducción a Funciones LENGUAJE DART FLUTTERIntroducción a Funciones LENGUAJE DART FLUTTER
Introducción a Funciones LENGUAJE DART FLUTTER
 
PARTES DEL TECLADO Y SUS FUNCIONES - EJEMPLO
PARTES DEL TECLADO Y SUS FUNCIONES - EJEMPLOPARTES DEL TECLADO Y SUS FUNCIONES - EJEMPLO
PARTES DEL TECLADO Y SUS FUNCIONES - EJEMPLO
 
Unidad_3_T1_AutomatasFinitos presentacion
Unidad_3_T1_AutomatasFinitos presentacionUnidad_3_T1_AutomatasFinitos presentacion
Unidad_3_T1_AutomatasFinitos presentacion
 

Legacy monolito y rankings de puntuaciones

  • 1. @rafaoe ! @JavierCane " ⛑ Aprende a convivir con el monolito legacy (o no) Volumen II
  • 2. Todo lo que veréis en esta charla HA PASADO
  • 3. Los hechos, nombres, y tips son semi-REALES
  • 4. En algunos tips esta charla podría HERIR SENSIBILIDADES
  • 6. La realidad y el legacy no entienden de sensibilidades
  • 10.
  • 11.
  • 12.
  • 13.
  • 14. Ey, ey. Hay código… pero no hay test
  • 15.
  • 16. UserController.java package com.codely.controller; import com.codely.model.User; import com.codely.repository.UserRepository; import com.codely.service.CacheService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Optional; @RestController @RequestMapping("/user") public class UserController { @GetMapping("/{id}") public ResponseEntity<User> getUser(@PathVariable("id") Long id) {
  • 17. UserController.java @RequestMapping("/user") public class UserController { @GetMapping("/{id}") public ResponseEntity<User> getUser(@PathVariable("id") Long id) { CacheService cacheService = CacheService.getInstance(); String cacheKey = "user_" + id; User user = cacheService.get(cacheKey, User.class); if (user != null) { return new ResponseEntity<>(user, HttpStatus.OK); } UserRepository userRepository = UserRepository.getInstance(); Optional<User> userOptional = userRepository.findById(id); if (userOptional.isEmpty()) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } user = userOptional.get(); cacheService.put(cacheKey, user);
  • 18. @RequestMapping("/user") public class UserController { @GetMapping("/{id}") public ResponseEntity<User> getUser(@PathVariable("id") Long id) { CacheService cacheService = CacheService.getInstance(); String cacheKey = "user_" + id; User user = cacheService.get(cacheKey, User.class); if (user != null) { return new ResponseEntity<>(user, HttpStatus.OK); } UserRepository userRepository = UserRepository.getInstance(); Optional<User> userOptional = userRepository.findById(id); if (userOptional.isEmpty()) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } user = userOptional.get(); cacheService.put(cacheKey, user); % % UserController.java
  • 19. Flujo de una petición & Infraestructura Aplicación Dominio
  • 20. Flujo de una petición & Unit test Integration test I A D Repository Controller UseCase Service Implementation Acceptance test
  • 21. Flujo de una petición & Repository Controller UseCase Service Unit test Integration test Implementation I A D Acceptance test ' ( )
  • 22. Flujo de una petición & Repository Controller UseCase Service Unit test Integration test Implementation Acceptance test I A D *
  • 23. Flujo de una petición & Repository Controller UseCase Service Unit test Integration test Implementation Acceptance test I A D $
  • 24. Aprendizajes ⏩ , No hay tests → Aceptación ⬇ Dump diario de la BD de prod . Dump selectivo / Datos al vuelo
  • 26. ⏩ Pasan 3 años
  • 27. Primera tarea en lo nuevo
  • 28. SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore
  • 29. SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM score WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ?
  • 30. SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM score WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM score WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM score WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM score WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM score WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRank FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY t.country_id ORDER BY t.highScore DESC) AS countryRank, RANK() OVER (ORDER BY t.highScore DESC) AS globalRan FROM ( SELECT user_id, country_id, MAX(score) AS highScore FROM ( SELECT * FROM ( SELECT t.user_id, t.country_id, t.highScore, RANK() OVER (PARTITION BY ORDER BY t.highScore RANK() OVER (ORDER BY t.h FROM ( SELECT user_id, country_id, MAX(score) AS hi FROM score WHERE topic_id = 101 GROUP BY user_id, co ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ? ) WHERE topic_id = 101 GROUP BY user_id, country_id ) AS t ) AS rt WHERE rt.user_id = ?
  • 31. Flujo de una petición & Repository Controller UseCase Service Unit test Integration test Implementation Acceptance test I A D *
  • 32. Flujo de una petición & Repository Controller UseCase Service Unit test Integration test Implementation Acceptance test I A D
  • 33. TopGamesRepository interface TopGamesRepository { public save(TopGame[] games): void; public searchAll(): TopGame[]; }
  • 34. Cómo hacer que sea un éxito Ey, ey. He gamificado la migración
  • 35.
  • 36. MySqlRepository.php abstract class MySqlRepository { public function search($query, $tag) { $taggedQuery = "/*"!999999 $tag */$ $query"; // ... } }
  • 37. OtherSqlRepository.php abstract class OtherSqlRepository { public function search($query, $tag) { $taggedQuery = "IF('$tag' = '', 1, 0) AS tagged $query"; // ... } }
  • 38. ⏩ Pasan 10 años
  • 39. 0 1 Evolución tamaño instancias EC2 2 r6a.metal r6a.48xlarge r6a.48large r6a.large r6a.xlarge 4 5 ♻ 7 8
  • 40. Sincronización entre sistema legacy y el nuevo ♻ Legacy New TubiAPIradora legacy domain_events new replicate_from_legacy new domain_events legacy replicate_from_new Publica evento Replica Consume Publica evento Replica Consume Invoca API 9
  • 41. ¿Fantasía en los nombres de proyecto? ☝ Sí No
  • 42. TubiAPIradora vs CDC ☝ Change Data Capture Pattern Consume Trigger BD TubiAPIradora bit.ly/codely-eda
  • 43. TubiAPIradora vs CDC ☝ Change Data Capture Pattern Consume Trigger BD TubiAPIradora bit.ly/codely-eda
  • 44. Cómo hacer que sea un éxito
  • 45. LegacyEventBus public publish(array $event): void { Publisher.publish( add( $event, ['metadata' =>& ['from_legacy' =>& true]] ) ); }
  • 46.
  • 47. Aprende a convivir con el monolito legacy (o no)
  • 48. Aprende a convivir con el monolito legacy
  • 49. Resultados encuesta ¿Nombres fantasiosos para proyectos? 2 0 25 50 75 100 0 100 No Sí bit.ly/hiring-codely ¡Muchas gracias! Nos vemos en Codely