Si bien no es un tema específico de Magento, voy a usarlo como referencia ya que es el código en el que vivo la mayor cantidad de horas del día, todos los días, desde hace, probablemente, demasiado tiempo.

Magento nos ofrece, de base, dos formas de agregar módulos:

/
|
+-- app
|   |
|   +-- code
|       |
|       +-- VendorName1
|       |   |
|       |   +-- ModuleName1
|       |
|       +-- VendorName2
|           |
|           +-- ModuleName1
|           |
|           +-- ModuleName2
|
+-- vendor
    |
    +-- VendorName3
    |   |
    |   +-- ModuleName1
    |
    +-- VendorName4
        |
        +-- ModuleName1

En la mayoría de los proyectos esos van a ser los lugares en donde encontremos los módulos.

Como ya vimos o suponemos, en /app/code ubicaremos los módulos únicos del proyecto, nuestro trabajo específico; mientras que en /vendor estarán los módulos instalados vía Composer y que, en su gran mayoría, son módulos de terceros o módulos que hicimos en el pasado y tenemos o en Packagist o bajo nuestro Satis u otra de las tantas alternativas existentes para consumir módulos distribuídos.

Existe una tercera opción que permite integrar lo mejor de los dos mundos cuando se trata resolver problemas de dependencias.

Supongamos que tengo dos módulos que desarrollo para un proyecto. Los ubico dentro de /app/code.

El módulo Merchants es, en lógica, dependiente del módulo Marketplace. Este es el composer.json del módulo Barbanet_Marketplace.

{
    "name": "barbanet/module-marketplace",
    "description": "Creates the base framework for the Marketplace",
    "type": "magento2-module",
    "license": "proprietary",
    "version": "0.1.0",
    "require": {
        "php": "~7.3.0||~7.4.0",
        "magento/framework": "*",
        "magento/module-store": "*"
    },
    "autoload": {
        "psr-4": {
            "Barbanet\\Marketplace\\": ""
        },
        "files": [
            "registration.php"
        ]
    }
}

El nombre para composer es barbanet/module-marketplace. Por el lado Magento, el archivo module.xml contiene esta definición.

<?xml version="1.0" encoding="UTF-8" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Barbanet_Marketplace">
        <sequence>
            <module name="Magento_Store" />
        </sequence>
    </module>
</config>

Como experimento, deshabilité el módulo Merchants e hice este cambio al camposer.json.

{
    "name": "barbanet/module-merchants",
    "description": "Merchant page",
    "type": "magento2-module",
    "license": "proprietary",
    "version": "0.1.0",
    "require": {
        "php": "~7.3.0||~7.4.0",
        "barbanet/module-marketplace2": "*",
        "magento/module-catalog": "*"
    },
    "autoload": {
        "psr-4": {
            "Barbanet\\Merchants\\": ""
        },
        "files": [
            "registration.php"
        ]
    }
}

En el require le indico que como dependencia tiene al módulo barbanet/module-marketplace2 (el cual no existe, sólo existe barbanet/module-marketplace). En el archivo module.xml ahora tengo esta definición para el módulo Merchants.

<?xml version="1.0" encoding="UTF-8" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Barbanet_Merchants">
        <sequence>
            <module name="Barbanet_Marketplace2" />
            <module name="Magento_Catalog" />
        </sequence>
    </module>
</config>

Otra vez, en lugar de usar Barbanet_Marketplace, estoy indicando un módulo que no tengo realmente.

Luego de esto ejecuté:

bin/magento module:enable Barbanet_Merchants

Y el resultado fue:

The following modules have been enabled:
- Barbanet_Merchants

To make sure that the enabled modules are properly registered, run 'setup:upgrade'.
Cache cleared successfully.
Generated classes cleared successfully. Please run the 'setup:di:compile' command to generate classes.
Info: Some modules might require static view files to be cleared. To do this, run 'module:enable' with the --clear-static-content option to clear them

Como ya suponíamos/sabíamos, el archivo composer.json tiene menos importancia que un cero a la izquierda. Y luego, lo que se indica en module.xml sólo sirve para indicar orden de carga del módulo, no dependencia en sentido estricto.

¿Cómo hacer entonces para tener módulos locales pero que puedan controlar dependencias de alguna manera?.

La solución viene de la mano de los repositorios (en términos de Composer), en particular el tipo path.

A nuestro archivo composer.json del proyecto le agregamos:

Lo que hago es definir un nuevo repositorio, de tupo path, que está en el directorio/url extensions (a la altura del composer.json) y uso comodines para los dos niveles de directorio que serán en donde se encontrarán los módulos.

El siguiente paso es mover mis módulo de /app/code dentro del directorio extensions (primero los deshabilité en Magento). Vamos a usar el nombre composer del módulo, no el nombre Magento.

Y voy a la consola y ejecuto:

composer require barbanet/module-marketplace

Veamos el resultado.

Listo, el módulo se instaló. Podemos ver aquí desde dónde.

Si miramos el composer.json del proyecto veremos que nuestro módulo fue incluído (obviamente, sino lo anterior no podría haber sucedido).

Y si miro los archivos dentro de /vendor voy a encontrar que:

El módulo se instaló, y como es un symlink no voy a tener problemas para desarrollo. (Modifico en /extensions y si ya está instalado, el cambio es inmediato).

¿En dónde está lo interesante para mi con este cambio?. Es que puedo controlar las dependencias incluso con mis módulos locales.

Antes había cambiado la dependencia de Barbanet_Merchants a Barbanet_Marketplace2 y pude habilitar el módulo sin problemas.

Veamos qué pasa ahora al ejecutar:

composer require barbanet/module-merchants

El resultado fue:

Composer hizo su trabajo, tal como esperaba. Desde ahora puedo controlar las dependencias, no sólo de un módulo dado, sino incluso de las versiones (algo muy muy útil para proyectos complejos).

Luego de corregir la dependencia del módulo volví a intentar.

Ahora si, todo funcionó como se esperaba.

Todo en su lugar.

Si ahora ejecuto un bin/magento module:status veré:

Efectivamente mis módulos están listos para ser habilitados. De aquí en más, todo como siempre.

De ésta manera tenemos la oportunidad de mover todos nuestros módulos, incluso los de terceros que nos tiran un .zip por la cabeza, debajo del paraguas de Composer.