A meu ver, você pode resolver isso de duas maneiras:
Categoria é um tipo especial de produto
Isso significa que, para qualquer produto em seu banco de dados, ele contém uma chave estrangeira apontando para o mesmo produto da tabela. Um produto é um produto apenas se não existirem produtos cuja chave estrangeira seja igual à identificação do referido produto. Em outras palavras, se ele não possui produtos, é um produto.
Isso simplificaria um pouco as coisas. Os produtos para propriedades teriam um relacionamento um para muitos e, portanto, suas categorias também terão um relacionamento para muitos, pois também são produtos. Adicionar uma propriedade a uma categoria seria tão fácil quanto adicionar uma propriedade a um produto no seu programa. Carregar todas as propriedades significaria combinar as propriedades do produto com as propriedades do produto de categoria associado e até chegar a um produto de categoria sem pai.
Seu aplicativo de comércio eletrônico precisaria fazer essa distinção, mas se você provavelmente carregar produtos de uma categoria de qualquer maneira, não será uma perda de desempenho saber se você está lidando com uma categoria ou um produto. Também se presta a pesquisar na forma de árvores ao nível do produto, pois cada produto (categoria) abriria uma lista de subprodutos sem muito trabalho adicional.
A desvantagem disso é, obviamente, as informações extras presentes no produto que não fazem sentido para uma categoria criariam campos não utilizados no produto. Embora essa solução seja mais flexível em seu aplicativo, também é um pouco menos intuitiva.
Relacionamento muitos-para-muitos
Os produtos não estão mais em um relacionamento composto com a propriedade. Você cria uma tabela ProductProperty com chaves estrangeiras da tabela do produto e da tabela de propriedades que vinculam as duas. Da mesma forma, você tem uma tabela de categorias com um relacionamento muitos para muitos com a tabela de propriedades e uma tabela CategoryProperty com chaves estrangeiras da tabela de categorias e da tabela de propriedades.
O produto em si teria um relacionamento muitos-para-um com a categoria, permitindo que você crie essencialmente uma lista de propriedades exclusivas pertencentes ao produto e à categoria por meio de uma instrução de seleção bem formalizada.
Do ponto de vista do banco de dados, isso é definitivamente mais limpo e mais flexível. Provavelmente, seu aplicativo pode funcionar sem a necessidade de lidar diretamente com CategoryProperty ou ProductProperty se a consulta for feita corretamente. No entanto, você também não deve tratar a categoria ou o produto como proprietário da propriedade. Deve ser sua própria entidade dentro do seu programa. Isso também significa que o gerenciamento das referidas propriedades seria uma questão de criar a própria propriedade e associá-la a uma categoria ou produto em duas etapas separadas. Certamente mais trabalho que a primeira solução, mas de nenhuma maneira mais difícil.
Além disso, você também teria que executar uma verificação adicional ao excluir a categoria ou o produto se alguma de suas propriedades estiver sendo usada por outras pessoas (diferente da primeira solução em que você pode eliminar com segurança todas as propriedades associadas de um determinado produto / categoria) .
Conclusão
Em um contexto profissional, eu iria além da categoria de milhas e distâncias entre produtos e produtos e propriedades, usando a abordagem muitos para muitos. Não haveria possibilidade de sobreposição de dados e, em certo sentido, é mais fácil raciocinar cada um desses três como sua própria entidade. No entanto, de maneira alguma a primeira solução é ruim, pois também permite que você escreva um aplicativo mais simples. Apenas saiba que, se você pensou que poderia eventualmente mudar de uma solução para outra, provavelmente seria do seu interesse escolher a segunda.
Boa sorte!